sys/vfs/hammer/hammer_object.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422 1423 1424 1425 1426 1427 1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 1438 1439 1440 1441 1442 1443 1444 1445 1446 1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 1458 1459 1460 1461 1462 1463 1464 1465 1466 1467 1468 1469 1470 1471 1472 1473 1474 1475 1476 1477 1478 1479 1480 1481 1482 1483 1484 1485 1486 1487 1488 1489 1490 1491 1492 1493 1494 1495 1496 1497 1498 1499 1500 1501 1502 1503 1504 1505 1506 1507 1508 1509 1510 1511 1512 1513 1514 1515 1516 1517 1518 1519 1520 1521 1522 1523 1524 1525 1526 1527 1528 1529 1530 1531 1532 1533 1534 1535 1536 1537 1538 1539 1540 1541 1542 1543 1544 1545 1546 1547 1548 1549 1550 1551 1552 1553 1554 1555 1556 1557 1558 1559 1560 1561 1562 1563 1564 1565 1566 1567 1568 1569 1570 1571 1572 1573 1574 1575 1576 1577 1578 1579 1580 1581 1582 1583 1584 1585 1586 1587 1588 1589 1590 1591 1592 1593 1594 1595 1596 1597 1598 1599 1600 1601 1602 1603 1604 1605 1606 1607 1608 1609 1610 1611 1612 1613 1614 1615 1616 1617 1618 1619 1620 1621 1622 1623 1624 1625 1626 1627 1628 1629 1630 1631 1632 1633 1634 1635 1636 1637 1638 1639 1640 1641 1642 1643 1644 1645 1646 1647 1648 1649 1650 1651 1652 1653 1654 1655 1656 1657 1658 1659 1660 1661 1662 1663 1664 1665 1666 1667 1668 1669 1670 1671 1672 1673 1674 1675 1676 1677 1678 1679 1680 1681 1682 1683 1684 1685 1686 1687 1688 1689 1690 1691 1692 1693 1694 1695 1696 1697 1698 1699 1700 1701 1702 1703 1704 1705 1706 1707 1708 1709 1710 1711 1712 1713 1714 1715 1716 1717 1718 1719 1720 1721 1722 1723 1724 1725 1726 1727 1728 1729 1730 1731 1732 1733 1734 1735 1736 1737 1738 1739 1740 1741 1742 1743 1744 1745 1746 1747 1748 1749 1750 1751 1752 1753 1754 1755 1756 1757 1758 1759 1760 1761 1762 1763 1764 1765 1766 1767 1768 1769 1770 1771 1772 1773 1774 1775 1776 1777 1778 1779 1780 1781 1782 1783 1784 1785 1786 1787 1788 1789 1790 1791 1792 1793 1794 1795 1796 1797 1798 1799 1800 1801 1802 1803 1804 1805 1806 1807 1808 1809 1810 1811 1812 1813 1814 1815 1816 1817 1818 1819 1820 1821 1822 1823 1824 1825 1826 1827 1828 1829 1830 1831 1832 1833 1834 1835 1836 1837 1838 1839 1840 1841 1842 1843 1844 1845 1846 1847 1848 1849 1850 1851 1852 1853 1854 1855 1856 1857 1858 1859 1860 1861 1862 1863 1864 1865 1866 1867 1868 1869 1870 1871 1872 1873 1874 1875 1876 1877 1878 1879 1880 1881 1882 1883 1884 1885 1886 1887 1888 1889 1890 1891 1892 1893 1894 1895 1896 1897 1898 1899 1900 1901 1902 1903 1904 1905 1906 1907 1908 1909 1910 1911 1912 1913 1914 1915 1916 1917 1918 1919 1920 1921 1922 1923 1924 1925 1926 1927 1928 1929 1930 1931 1932 1933 1934 1935 1936 1937 1938 1939 1940 1941 1942 1943 1944 1945 1946 1947 1948 1949 1950 1951 1952 1953 1954 1955 1956 1957 1958 1959 1960 1961 1962 1963 1964 1965 1966 1967 1968 1969 1970 1971 1972 1973 1974 1975 1976 1977 1978 1979 1980 1981 1982 1983 1984 1985 1986 1987 1988 1989 1990 1991 1992 1993 1994 1995 1996 1997 1998 1999 2000 2001 2002 2003 2004 2005 2006 2007 2008 2009 2010 2011 2012 2013 2014 2015 2016 2017 2018 2019 2020 2021 2022 2023 2024 2025 2026 2027 2028 2029 2030 2031 2032 2033 2034 2035 2036 2037 2038 2039 2040 2041 2042 2043 2044 2045 2046 2047 2048 2049 2050 2051 2052 2053 2054 2055 2056 2057 2058 2059 2060 2061 2062 2063 2064 2065 2066 2067 2068 2069 2070 2071 2072 2073 2074 2075 2076 2077 2078 2079 2080 2081 2082 2083 2084 2085 2086 2087 2088 2089 2090 2091 2092 2093 2094 2095 2096 2097 2098 2099 2100 2101 2102 2103 2104 2105 2106 2107 2108 2109 2110 2111 2112 2113 2114 2115 2116 2117 2118 2119 2120 2121 2122 2123 2124 2125 2126 2127 2128 2129 2130 2131 2132 2133 2134 2135 2136 2137 2138 2139 2140 2141 2142 2143 2144 2145 2146 2147 2148 2149 2150 2151 2152 2153 2154 2155 2156 2157 2158 2159 2160 2161 2162 2163 2164 2165 2166 2167 2168 2169 2170 2171 2172 2173 2174 2175 2176 2177 2178 2179 2180 2181 2182 2183 2184 2185 2186 2187 2188 2189 2190 2191 2192 2193 2194 2195 2196 2197 2198 2199 2200 2201 2202 2203 2204 2205 2206 2207 2208 2209 2210 2211 2212 2213 2214 2215 2216 2217 2218 2219 2220 2221 2222 2223 2224 2225 2226 2227 2228 2229 2230 2231 2232 2233 2234 2235 2236 2237 2238 2239 2240 2241 2242 2243 2244 2245 2246 2247 2248 2249 2250 2251 2252 2253 2254 2255 2256 2257 2258 2259 2260 2261 2262 2263 2264 2265 2266 2267 2268 2269 2270 2271 2272 2273 2274 2275 2276 2277 2278 2279 2280 2281 2282 2283 2284 2285 2286 2287 2288 2289 2290 2291 2292 2293 2294 2295 2296 2297 2298 2299 2300 2301 2302 2303 2304 2305 2306 2307 2308 2309 2310 2311 2312 2313 2314 2315 2316 2317 2318 2319 2320 2321 2322 2323 2324 2325 2326 2327 2328 2329 2330 2331 2332 2333 2334 2335 2336 2337 2338 2339 2340 2341 2342 2343 2344 2345 2346 2347 2348 2349 2350 2351 2352 2353 2354 2355 2356 2357 2358 2359 2360 2361 2362 2363 2364 2365 2366 2367 2368 2369 2370 2371 2372 2373 2374 2375 2376 2377 2378 2379 2380 2381 2382 2383 2384 2385 2386 2387 2388 2389 2390 2391 2392 2393 2394 2395 2396 2397 2398 2399 2400 2401 2402 2403 2404 2405 2406 2407 2408 2409 2410 2411 2412 2413 2414 2415 2416 2417 2418 2419 2420 2421 2422 2423 2424 2425 2426 2427 2428 2429 2430 2431 2432 2433 2434 2435 2436 2437 2438 2439 2440 2441 2442 2443 2444 2445 2446 2447 2448 2449 2450 2451 2452 2453 2454 2455 2456 2457 2458 2459 2460 2461 2462 2463 2464 2465 2466 2467 2468 2469 2470 2471 2472 2473 2474 2475 2476 2477 2478 2479 2480 2481 2482 2483 2484 2485 2486 2487 2488 2489 2490 2491 2492 2493 2494 2495 2496 2497 2498 2499 2500 2501 2502 2503 2504 2505 2506 2507 2508 2509 2510 2511 2512 2513 2514 2515 2516 2517 2518 2519 2520 2521 2522 2523 2524 2525 2526 2527 2528 2529 2530 2531 2532 2533 2534 2535 2536 2537 2538 2539 2540 2541 2542 2543 2544 2545 2546 2547 2548 2549 2550 2551 2552 2553 2554 2555 2556 2557 2558 2559 2560 2561 2562 2563 2564 2565 2566 2567 2568 2569 2570 2571 2572 2573 2574 2575 2576 2577 2578 2579 2580 2581 2582 2583 2584 2585 2586 2587 2588 2589 2590 2591 2592 2593 2594 2595 2596 2597 2598 2599 2600 2601 2602 2603 2604 2605 2606 2607 2608 2609 2610 2611 2612 2613 2614 2615 2616 2617 2618 2619 2620 2621 2622 2623 2624 2625 2626 2627 2628 2629 2630 2631 2632 2633 2634 2635 2636 2637 2638 2639 2640 2641 2642 2643 2644 2645 2646 2647 2648 2649 2650 2651 2652 2653 2654 2655 2656 2657 2658 2659 2660 2661 | /* * Copyright (c) 2007-2008 The DragonFly Project. All rights reserved. * * This code is derived from software contributed to The DragonFly Project * by Matthew Dillon <dillon@backplane.com> * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * 3. Neither the name of The DragonFly Project nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific, prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include "hammer.h" static int hammer_mem_lookup(hammer_cursor_t cursor); static int hammer_mem_first(hammer_cursor_t cursor); static int hammer_frontend_trunc_callback(hammer_record_t record, void *data __unused); static int hammer_bulk_scan_callback(hammer_record_t record, void *data); static int hammer_record_needs_overwrite_delete(hammer_record_t record); static int hammer_delete_general(hammer_cursor_t cursor, hammer_inode_t ip, hammer_btree_leaf_elm_t leaf); static int hammer_cursor_localize_data(hammer_mount_t hmp, hammer_data_ondisk_t data, hammer_btree_leaf_elm_t leaf); struct rec_trunc_info { uint16_t rec_type; int64_t trunc_off; }; struct hammer_bulk_info { hammer_record_t record; hammer_record_t conflict; }; /* * Red-black tree support. Comparison code for insertion. */ static int hammer_rec_rb_compare(hammer_record_t rec1, hammer_record_t rec2) { if (rec1->leaf.base.rec_type < rec2->leaf.base.rec_type) return(-1); if (rec1->leaf.base.rec_type > rec2->leaf.base.rec_type) return(1); if (rec1->leaf.base.key < rec2->leaf.base.key) return(-1); if (rec1->leaf.base.key > rec2->leaf.base.key) return(1); /* * For search & insertion purposes records deleted by the * frontend or deleted/committed by the backend are silently * ignored. Otherwise pipelined insertions will get messed * up. * * rec1 is greater then rec2 if rec1 is marked deleted. * rec1 is less then rec2 if rec2 is marked deleted. * * Multiple deleted records may be present, do not return 0 * if both are marked deleted. */ if (rec1->flags & (HAMMER_RECF_DELETED_FE | HAMMER_RECF_DELETED_BE | HAMMER_RECF_COMMITTED)) { return(1); } if (rec2->flags & (HAMMER_RECF_DELETED_FE | HAMMER_RECF_DELETED_BE | HAMMER_RECF_COMMITTED)) { return(-1); } return(0); } /* * Basic record comparison code similar to hammer_btree_cmp(). * * obj_id is not compared and may not yet be assigned in the record. */ static int hammer_rec_cmp(hammer_base_elm_t elm, hammer_record_t rec) { if (elm->rec_type < rec->leaf.base.rec_type) return(-3); if (elm->rec_type > rec->leaf.base.rec_type) return(3); if (elm->key < rec->leaf.base.key) return(-2); if (elm->key > rec->leaf.base.key) return(2); /* * Never match against an item deleted by the frontend * or backend, or committed by the backend. * * elm is less then rec if rec is marked deleted. */ if (rec->flags & (HAMMER_RECF_DELETED_FE | HAMMER_RECF_DELETED_BE | HAMMER_RECF_COMMITTED)) { return(-1); } return(0); } /* * Ranged scan to locate overlapping record(s). This is used by * hammer_ip_get_bulk() to locate an overlapping record. We have * to use a ranged scan because the keys for data records with the * same file base offset can be different due to differing data_len's. * * NOTE: The base file offset of a data record is (key - data_len), not (key). */ static int hammer_rec_overlap_cmp(hammer_record_t rec, void *data) { struct hammer_bulk_info *info = data; hammer_btree_leaf_elm_t leaf = &info->record->leaf; if (rec->leaf.base.rec_type < leaf->base.rec_type) return(-3); if (rec->leaf.base.rec_type > leaf->base.rec_type) return(3); /* * Overlap compare */ if (leaf->base.rec_type == HAMMER_RECTYPE_DATA) { /* rec_beg >= leaf_end */ if (rec->leaf.base.key - rec->leaf.data_len >= leaf->base.key) return(2); /* rec_end <= leaf_beg */ if (rec->leaf.base.key <= leaf->base.key - leaf->data_len) return(-2); } else { if (rec->leaf.base.key < leaf->base.key) return(-2); if (rec->leaf.base.key > leaf->base.key) return(2); } /* * We have to return 0 at this point, even if DELETED_FE is set, * because returning anything else will cause the scan to ignore * one of the branches when we really want it to check both. */ return(0); } /* * RB_SCAN comparison code for hammer_mem_first(). The argument order * is reversed so the comparison result has to be negated. key_beg and * key_end are both range-inclusive. * * Localized deletions are not cached in-memory. */ static int hammer_rec_scan_cmp(hammer_record_t rec, void *data) { hammer_cursor_t cursor = data; int r; r = hammer_rec_cmp(&cursor->key_beg, rec); if (r > 1) return(-1); r = hammer_rec_cmp(&cursor->key_end, rec); if (r < -1) return(1); return(0); } /* * This compare function is used when simply looking up key_beg. */ static int hammer_rec_find_cmp(hammer_record_t rec, void *data) { hammer_cursor_t cursor = data; int r; r = hammer_rec_cmp(&cursor->key_beg, rec); if (r > 1) return(-1); if (r < -1) return(1); return(0); } /* * Locate blocks within the truncation range. Partial blocks do not count. */ static int hammer_rec_trunc_cmp(hammer_record_t rec, void *data) { struct rec_trunc_info *info = data; if (rec->leaf.base.rec_type < info->rec_type) return(-1); if (rec->leaf.base.rec_type > info->rec_type) return(1); switch(rec->leaf.base.rec_type) { case HAMMER_RECTYPE_DB: /* * DB record key is not beyond the truncation point, retain. */ if (rec->leaf.base.key < info->trunc_off) return(-1); break; case HAMMER_RECTYPE_DATA: /* * DATA record offset start is not beyond the truncation point, * retain. */ if (rec->leaf.base.key - rec->leaf.data_len < info->trunc_off) return(-1); break; default: hpanic("unexpected record type"); } /* * The record start is >= the truncation point, return match, * the record should be destroyed. */ return(0); } RB_GENERATE(hammer_rec_rb_tree, hammer_record, rb_node, hammer_rec_rb_compare); /* * Allocate a record for the caller to finish filling in. The record is * returned referenced. In order to manually set data call this function * with data_len=0 and then manually set record->leaf.data_len and * record->data later. */ hammer_record_t hammer_alloc_mem_record(hammer_inode_t ip, int data_len) { hammer_record_t record; hammer_mount_t hmp; hmp = ip->hmp; ++hammer_count_records; record = kmalloc(sizeof(*record), hmp->m_misc, M_WAITOK | M_ZERO | M_USE_RESERVE); record->flush_state = HAMMER_FST_IDLE; record->ip = ip; record->leaf.base.btype = HAMMER_BTREE_TYPE_RECORD; record->leaf.data_len = data_len; hammer_ref(&record->lock); if (data_len) { record->data = kmalloc(data_len, hmp->m_misc, M_WAITOK | M_ZERO); record->flags |= HAMMER_RECF_ALLOCDATA; ++hammer_count_record_datas; } return (record); } void hammer_wait_mem_record_ident(hammer_record_t record, const char *ident) { while (record->flush_state == HAMMER_FST_FLUSH) { record->flags |= HAMMER_RECF_WANTED; tsleep(record, 0, ident, 0); } } /* * Called from the backend, hammer_inode.c, after a record has been * flushed to disk. The record has been exclusively locked by the * caller and interlocked with BE. * * We clean up the state, unlock, and release the record (the record * was referenced by the fact that it was in the HAMMER_FST_FLUSH state). */ void hammer_flush_record_done(hammer_record_t record, int error) { hammer_inode_t target_ip; KKASSERT(record->flush_state == HAMMER_FST_FLUSH); KKASSERT(record->flags & HAMMER_RECF_INTERLOCK_BE); /* * If an error occured, the backend was unable to sync the * record to its media. Leave the record intact. */ if (error) { hammer_critical_error(record->ip->hmp, record->ip, error, "while flushing record"); } --record->flush_group->refs; record->flush_group = NULL; /* * Adjust the flush state and dependancy based on success or * failure. */ if (record->flags & (HAMMER_RECF_DELETED_BE | HAMMER_RECF_COMMITTED)) { if ((target_ip = record->target_ip) != NULL) { TAILQ_REMOVE(&target_ip->target_list, record, target_entry); record->target_ip = NULL; hammer_test_inode(target_ip); } record->flush_state = HAMMER_FST_IDLE; } else { if (record->target_ip) { record->flush_state = HAMMER_FST_SETUP; hammer_test_inode(record->ip); hammer_test_inode(record->target_ip); } else { record->flush_state = HAMMER_FST_IDLE; } } record->flags &= ~HAMMER_RECF_INTERLOCK_BE; /* * Cleanup */ if (record->flags & HAMMER_RECF_WANTED) { record->flags &= ~HAMMER_RECF_WANTED; wakeup(record); } hammer_rel_mem_record(record); } /* * Release a memory record. Records marked for deletion are immediately * removed from the RB-Tree but otherwise left intact until the last ref * goes away. */ void hammer_rel_mem_record(hammer_record_t record) { hammer_mount_t hmp; hammer_reserve_t resv; hammer_inode_t ip; hammer_inode_t target_ip; int diddrop; hammer_rel(&record->lock); if (hammer_norefs(&record->lock)) { /* * Upon release of the last reference wakeup any waiters. * The record structure may get destroyed so callers will * loop up and do a relookup. * * WARNING! Record must be removed from RB-TREE before we * might possibly block. hammer_test_inode() can block! */ ip = record->ip; hmp = ip->hmp; /* * Upon release of the last reference a record marked deleted * by the front or backend, or committed by the backend, * is destroyed. */ if (record->flags & (HAMMER_RECF_DELETED_FE | HAMMER_RECF_DELETED_BE | HAMMER_RECF_COMMITTED)) { KKASSERT(hammer_isactive(&ip->lock) > 0); KKASSERT(record->flush_state != HAMMER_FST_FLUSH); /* * target_ip may have zero refs, we have to ref it * to prevent it from being ripped out from under * us. */ if ((target_ip = record->target_ip) != NULL) { TAILQ_REMOVE(&target_ip->target_list, record, target_entry); record->target_ip = NULL; hammer_ref(&target_ip->lock); } /* * Remove the record from the RB-Tree */ if (record->flags & HAMMER_RECF_ONRBTREE) { RB_REMOVE(hammer_rec_rb_tree, &ip->rec_tree, record); record->flags &= ~HAMMER_RECF_ONRBTREE; KKASSERT(ip->rsv_recs > 0); if (RB_EMPTY(&ip->rec_tree)) { ip->flags &= ~HAMMER_INODE_XDIRTY; ip->sync_flags &= ~HAMMER_INODE_XDIRTY; } diddrop = 1; } else { diddrop = 0; } /* * We must wait for any direct-IO to complete before * we can destroy the record because the bio may * have a reference to it. */ if (record->gflags & (HAMMER_RECG_DIRECT_IO | HAMMER_RECG_DIRECT_INVAL)) { hammer_io_direct_wait(record); } /* * Account for the completion after the direct IO * has completed. */ if (diddrop) { --hmp->rsv_recs; --ip->rsv_recs; hmp->rsv_databytes -= record->leaf.data_len; if (RB_EMPTY(&ip->rec_tree)) hammer_test_inode(ip); if ((ip->flags & HAMMER_INODE_RECSW) && ip->rsv_recs <= hammer_limit_inode_recs/2) { ip->flags &= ~HAMMER_INODE_RECSW; wakeup(&ip->rsv_recs); } } /* * Do this test after removing record from the RB-Tree. */ if (target_ip) { hammer_test_inode(target_ip); hammer_rel_inode(target_ip, 0); } if (record->flags & HAMMER_RECF_ALLOCDATA) { --hammer_count_record_datas; kfree(record->data, hmp->m_misc); record->flags &= ~HAMMER_RECF_ALLOCDATA; } /* * Release the reservation. * * If the record was not committed we can theoretically * undo the reservation. However, doing so might * create weird edge cases with the ordering of * direct writes because the related buffer cache * elements are per-vnode. So we don't try. */ if ((resv = record->resv) != NULL) { /* XXX undo leaf.data_offset,leaf.data_len */ hammer_blockmap_reserve_complete(hmp, resv); record->resv = NULL; } record->data = NULL; --hammer_count_records; kfree(record, hmp->m_misc); } } } /* * Record visibility depends on whether the record is being accessed by * the backend or the frontend. Backend tests ignore the frontend delete * flag. Frontend tests do NOT ignore the backend delete/commit flags and * must also check for commit races. * * Return non-zero if the record is visible, zero if it isn't or if it is * deleted. Returns 0 if the record has been comitted (unless the special * delete-visibility flag is set). A committed record must be located * via the media B-Tree. Returns non-zero if the record is good. * * If HAMMER_CURSOR_DELETE_VISIBILITY is set we allow deleted memory * records to be returned. This is so pending deletions are detected * when using an iterator to locate an unused hash key, or when we need * to locate historical records on-disk to destroy. */ static __inline int hammer_ip_iterate_mem_good(hammer_cursor_t cursor, hammer_record_t record) { if (cursor->flags & HAMMER_CURSOR_DELETE_VISIBILITY) return(1); if (cursor->flags & HAMMER_CURSOR_BACKEND) { if (record->flags & (HAMMER_RECF_DELETED_BE | HAMMER_RECF_COMMITTED)) { return(0); } } else { if (record->flags & (HAMMER_RECF_DELETED_FE | HAMMER_RECF_DELETED_BE | HAMMER_RECF_COMMITTED)) { return(0); } } return(1); } /* * This callback is used as part of the RB_SCAN function for in-memory * records. We terminate it (return -1) as soon as we get a match. * * This routine is used by frontend code. * * The primary compare code does not account for ASOF lookups. This * code handles that case as well as a few others. */ static int hammer_rec_scan_callback(hammer_record_t rec, void *data) { hammer_cursor_t cursor = data; /* * We terminate on success, so this should be NULL on entry. */ KKASSERT(cursor->iprec == NULL); /* * Skip if the record was marked deleted or committed. */ if (hammer_ip_iterate_mem_good(cursor, rec) == 0) return(0); /* * Skip if not visible due to our as-of TID */ if (cursor->flags & HAMMER_CURSOR_ASOF) { if (cursor->asof < rec->leaf.base.create_tid) return(0); if (rec->leaf.base.delete_tid && cursor->asof >= rec->leaf.base.delete_tid) { return(0); } } /* * ref the record. The record is protected from backend B-Tree * interactions by virtue of the cursor's IP lock. */ hammer_ref(&rec->lock); /* * The record may have been deleted or committed while we * were blocked. XXX remove? */ if (hammer_ip_iterate_mem_good(cursor, rec) == 0) { hammer_rel_mem_record(rec); return(0); } /* * Set the matching record and stop the scan. */ cursor->iprec = rec; return(-1); } /* * Lookup an in-memory record given the key specified in the cursor. Works * just like hammer_btree_lookup() but operates on an inode's in-memory * record list. * * The lookup must fail if the record is marked for deferred deletion. * * The API for mem/btree_lookup() does not mess with the ATE/EOF bits. */ static int hammer_mem_lookup(hammer_cursor_t cursor) { KKASSERT(cursor->ip != NULL); if (cursor->iprec) { hammer_rel_mem_record(cursor->iprec); cursor->iprec = NULL; } hammer_rec_rb_tree_RB_SCAN(&cursor->ip->rec_tree, hammer_rec_find_cmp, hammer_rec_scan_callback, cursor); return (cursor->iprec ? 0 : ENOENT); } /* * hammer_mem_first() - locate the first in-memory record matching the * cursor within the bounds of the key range. * * WARNING! API is slightly different from btree_first(). hammer_mem_first() * will set ATEMEM the same as MEMEOF, and does not return any error. */ static int hammer_mem_first(hammer_cursor_t cursor) { KKASSERT(cursor->ip != NULL); if (cursor->iprec) { hammer_rel_mem_record(cursor->iprec); cursor->iprec = NULL; } hammer_rec_rb_tree_RB_SCAN(&cursor->ip->rec_tree, hammer_rec_scan_cmp, hammer_rec_scan_callback, cursor); if (cursor->iprec) cursor->flags &= ~(HAMMER_CURSOR_MEMEOF | HAMMER_CURSOR_ATEMEM); else cursor->flags |= HAMMER_CURSOR_MEMEOF | HAMMER_CURSOR_ATEMEM; return (cursor->iprec ? 0 : ENOENT); } /************************************************************************ * HAMMER IN-MEMORY RECORD FUNCTIONS * ************************************************************************ * * These functions manipulate in-memory records. Such records typically * exist prior to being committed to disk or indexed via the on-disk B-Tree. */ /* * Add a directory entry (dip,ncp) which references inode (ip). * * Note that the low 32 bits of the namekey are set temporarily to create * a unique in-memory record, and may be modified a second time when the * record is synchronized to disk. In particular, the low 32 bits cannot be * all 0's when synching to disk, which is not handled here. * * NOTE: bytes does not include any terminating \0 on name, and name might * not be terminated. */ int hammer_ip_add_direntry(hammer_transaction_t trans, hammer_inode_t dip, const char *name, int bytes, hammer_inode_t ip) { struct hammer_cursor cursor; hammer_record_t record; int error; uint32_t max_iterations; KKASSERT(dip->ino_data.obj_type == HAMMER_OBJTYPE_DIRECTORY); record = hammer_alloc_mem_record(dip, HAMMER_ENTRY_SIZE(bytes)); record->type = HAMMER_MEM_RECORD_ADD; record->leaf.base.localization = dip->obj_localization | hammer_dir_localization(dip); record->leaf.base.obj_id = dip->obj_id; record->leaf.base.key = hammer_direntry_namekey(dip, name, bytes, &max_iterations); record->leaf.base.rec_type = HAMMER_RECTYPE_DIRENTRY; record->leaf.base.obj_type = ip->ino_leaf.base.obj_type; record->data->entry.obj_id = ip->obj_id; record->data->entry.localization = ip->obj_localization; bcopy(name, record->data->entry.name, bytes); ++ip->ino_data.nlinks; ip->ino_data.ctime = trans->time; hammer_modify_inode(trans, ip, HAMMER_INODE_DDIRTY); /* * Find an unused namekey. Both the in-memory record tree and * the B-Tree are checked. We do not want historically deleted * names to create a collision as our iteration space may be limited, * and since create_tid wouldn't match anyway an ASOF search * must be used to locate collisions. * * delete-visibility is set so pending deletions do not give us * a false-negative on our ability to use an iterator. * * The iterator must not rollover the key. Directory keys only * use the positive key space. */ hammer_init_cursor(trans, &cursor, &dip->cache[1], dip); cursor.key_beg = record->leaf.base; cursor.flags |= HAMMER_CURSOR_ASOF; cursor.flags |= HAMMER_CURSOR_DELETE_VISIBILITY; cursor.asof = ip->obj_asof; while (hammer_ip_lookup(&cursor) == 0) { ++record->leaf.base.key; KKASSERT(record->leaf.base.key > 0); cursor.key_beg.key = record->leaf.base.key; if (--max_iterations == 0) { hammer_rel_mem_record(record); hmkprintf(trans->hmp, "Failed to find an unused namekey\n"); error = ENOSPC; goto failed; } } /* * The target inode and the directory entry are bound together. */ record->target_ip = ip; record->flush_state = HAMMER_FST_SETUP; TAILQ_INSERT_TAIL(&ip->target_list, record, target_entry); /* * The inode now has a dependancy and must be taken out of the idle * state. An inode not in an idle state is given an extra reference. * * When transitioning to a SETUP state flag for an automatic reflush * when the dependancies are disposed of if someone is waiting on * the inode. */ if (ip->flush_state == HAMMER_FST_IDLE) { hammer_ref(&ip->lock); ip->flush_state = HAMMER_FST_SETUP; if (ip->flags & HAMMER_INODE_FLUSHW) ip->flags |= HAMMER_INODE_REFLUSH; } error = hammer_mem_add(record); if (error == 0) { dip->ino_data.mtime = trans->time; dip->ino_data.ctime = trans->time; hammer_modify_inode(trans, dip, HAMMER_INODE_MTIME | HAMMER_INODE_DDIRTY); } failed: hammer_done_cursor(&cursor); return(error); } /* * Delete the directory entry and update the inode link count. The * cursor must be seeked to the directory entry record being deleted. * * The related inode should be share-locked by the caller. The caller is * on the frontend. It could also be NULL indicating that the directory * entry being removed has no related inode. * * This function can return EDEADLK requiring the caller to terminate * the cursor, any locks, wait on the returned record, and retry. */ int hammer_ip_del_direntry(hammer_transaction_t trans, hammer_cursor_t cursor, hammer_inode_t dip, hammer_inode_t ip) { hammer_record_t record; int error; if (hammer_cursor_inmem(cursor)) { /* * In-memory (unsynchronized) records can simply be freed. * * Even though the HAMMER_RECF_DELETED_FE flag is ignored * by the backend, we must still avoid races against the * backend potentially syncing the record to the media. * * We cannot call hammer_ip_delete_record(), that routine may * only be called from the backend. */ record = cursor->iprec; if (record->flags & (HAMMER_RECF_INTERLOCK_BE | HAMMER_RECF_DELETED_BE | HAMMER_RECF_COMMITTED)) { KKASSERT(cursor->deadlk_rec == NULL); hammer_ref(&record->lock); cursor->deadlk_rec = record; error = EDEADLK; } else { KKASSERT(record->type == HAMMER_MEM_RECORD_ADD); record->flags |= HAMMER_RECF_DELETED_FE; error = 0; } } else { /* * If the record is on-disk we have to queue the deletion by * the record's key. This also causes lookups to skip the * record (lookups for the purposes of finding an unused * directory key do not skip the record). */ KKASSERT(dip->flags & (HAMMER_INODE_ONDISK | HAMMER_INODE_DONDISK)); record = hammer_alloc_mem_record(dip, 0); record->type = HAMMER_MEM_RECORD_DEL; record->leaf.base = cursor->leaf->base; KKASSERT(dip->obj_id == record->leaf.base.obj_id); /* * ip may be NULL, indicating the deletion of a directory * entry which has no related inode. */ record->target_ip = ip; if (ip) { record->flush_state = HAMMER_FST_SETUP; TAILQ_INSERT_TAIL(&ip->target_list, record, target_entry); } else { record->flush_state = HAMMER_FST_IDLE; } /* * The inode now has a dependancy and must be taken out of * the idle state. An inode not in an idle state is given * an extra reference. * * When transitioning to a SETUP state flag for an automatic * reflush when the dependancies are disposed of if someone * is waiting on the inode. */ if (ip && ip->flush_state == HAMMER_FST_IDLE) { hammer_ref(&ip->lock); ip->flush_state = HAMMER_FST_SETUP; if (ip->flags & HAMMER_INODE_FLUSHW) ip->flags |= HAMMER_INODE_REFLUSH; } error = hammer_mem_add(record); } /* * One less link. The file may still be open in the OS even after * all links have gone away. * * We have to terminate the cursor before syncing the inode to * avoid deadlocking against ourselves. XXX this may no longer * be true. * * If nlinks drops to zero and the vnode is inactive (or there is * no vnode), call hammer_inode_unloadable_check() to zonk the * inode. If we don't do this here the inode will not be destroyed * on-media until we unmount. */ if (error == 0) { if (ip) { --ip->ino_data.nlinks; /* do before we might block */ ip->ino_data.ctime = trans->time; } dip->ino_data.mtime = trans->time; hammer_modify_inode(trans, dip, HAMMER_INODE_MTIME); if (ip) { hammer_modify_inode(trans, ip, HAMMER_INODE_DDIRTY); if (ip->ino_data.nlinks == 0 && (ip->vp == NULL || (ip->vp->v_flag & VINACTIVE))) { hammer_done_cursor(cursor); hammer_inode_unloadable_check(ip, 1); hammer_flush_inode(ip, 0); } } } return(error); } /* * Add a record to an inode. * * The caller must allocate the record with hammer_alloc_mem_record(ip,len) and * initialize the following additional fields that are not initialized by these * functions. * * The related inode should be share-locked by the caller. The caller is * on the frontend. * * record->leaf.base.key * record->leaf.base.rec_type * record->leaf.base.localization */ int hammer_ip_add_record(hammer_transaction_t trans, hammer_record_t record) { hammer_inode_t ip = record->ip; int error; KKASSERT(record->leaf.base.localization != 0); record->leaf.base.obj_id = ip->obj_id; record->leaf.base.obj_type = ip->ino_leaf.base.obj_type; error = hammer_mem_add(record); return(error); } /* * Locate a pre-existing bulk record in memory. The caller wishes to * replace the record with a new one. The existing record may have a * different length (and thus a different key) so we have to use an * overlap check function. */ static hammer_record_t hammer_ip_get_bulk(hammer_record_t record) { struct hammer_bulk_info info; hammer_inode_t ip = record->ip; info.record = record; info.conflict = NULL; hammer_rec_rb_tree_RB_SCAN(&ip->rec_tree, hammer_rec_overlap_cmp, hammer_bulk_scan_callback, &info); return(info.conflict); /* may be NULL */ } /* * Take records vetted by overlap_cmp. The first non-deleted record * (if any) stops the scan. */ static int hammer_bulk_scan_callback(hammer_record_t record, void *data) { struct hammer_bulk_info *info = data; if (record->flags & (HAMMER_RECF_DELETED_FE | HAMMER_RECF_DELETED_BE | HAMMER_RECF_COMMITTED)) { return(0); } hammer_ref(&record->lock); info->conflict = record; return(-1); /* stop scan */ } /* * Reserve blockmap space placemarked with an in-memory record. * * This routine is called by the frontend in order to be able to directly * flush a buffer cache buffer. The frontend has locked the related buffer * cache buffers and we should be able to manipulate any overlapping * in-memory records. * * The caller is responsible for adding the returned record and deleting * the returned conflicting record (if any), typically by calling * hammer_ip_replace_bulk() (via hammer_io_direct_write()). */ hammer_record_t hammer_ip_add_bulk(hammer_inode_t ip, off_t file_offset, void *data, int bytes, int *errorp) { hammer_record_t record; int zone; /* * Create a record to cover the direct write. The record cannot * be added to the in-memory RB tree here as it might conflict * with an existing memory record. See hammer_io_direct_write(). * * The backend is responsible for finalizing the space reserved in * this record. * * XXX bytes not aligned, depend on the reservation code to * align the reservation. */ record = hammer_alloc_mem_record(ip, 0); zone = hammer_data_zone_index(bytes); record->resv = hammer_blockmap_reserve(ip->hmp, zone, bytes, &record->leaf.data_offset, errorp); if (record->resv == NULL) { hdkprintf("reservation failed\n"); hammer_rel_mem_record(record); return(NULL); } record->type = HAMMER_MEM_RECORD_DATA; record->leaf.base.rec_type = HAMMER_RECTYPE_DATA; record->leaf.base.obj_type = ip->ino_leaf.base.obj_type; record->leaf.base.obj_id = ip->obj_id; record->leaf.base.key = file_offset + bytes; record->leaf.base.localization = ip->obj_localization | HAMMER_LOCALIZE_MISC; record->leaf.data_len = bytes; hammer_crc_set_leaf(ip->hmp->version, data, &record->leaf); KKASSERT(*errorp == 0); return(record); } /* * Called by hammer_io_direct_write() prior to any possible completion * of the BIO to emplace the memory record associated with the I/O and * to replace any prior memory record which might still be active. * * Setting the FE deleted flag on the old record (if any) avoids any RB * tree insertion conflict, amoung other things. * * This has to be done prior to the caller completing any related buffer * cache I/O or a reinstantiation of the buffer may load data from the * old media location instead of the new media location. The holding * of the locked buffer cache buffer serves to interlock the record * replacement operation. */ void hammer_ip_replace_bulk(hammer_mount_t hmp, hammer_record_t record) { hammer_record_t conflict; int error __debugvar; while ((conflict = hammer_ip_get_bulk(record)) != NULL) { if ((conflict->flags & HAMMER_RECF_INTERLOCK_BE) == 0) { conflict->flags |= HAMMER_RECF_DELETED_FE; break; } conflict->flags |= HAMMER_RECF_WANTED; tsleep(conflict, 0, "hmrrc3", 0); hammer_rel_mem_record(conflict); } error = hammer_mem_add(record); if (conflict) hammer_rel_mem_record(conflict); KKASSERT(error == 0); } /* * Frontend truncation code. Scan in-memory records only. On-disk records * and records in a flushing state are handled by the backend. The vnops * setattr code will handle the block containing the truncation point. * * Partial blocks are not deleted. * * This code is only called on regular files. */ int hammer_ip_frontend_trunc(hammer_inode_t ip, off_t file_size) { struct rec_trunc_info info; switch(ip->ino_data.obj_type) { case HAMMER_OBJTYPE_REGFILE: info.rec_type = HAMMER_RECTYPE_DATA; break; case HAMMER_OBJTYPE_DBFILE: info.rec_type = HAMMER_RECTYPE_DB; break; default: return(EINVAL); } info.trunc_off = file_size; hammer_rec_rb_tree_RB_SCAN(&ip->rec_tree, hammer_rec_trunc_cmp, hammer_frontend_trunc_callback, &info); return(0); } /* * Scan callback for frontend records to destroy during a truncation. * We must ensure that DELETED_FE is set on the record or the frontend * will get confused in future read() calls. * * NOTE: DELETED_FE cannot be set while the record interlock (BE) is held. * In this rare case we must wait for the interlock to be cleared. * * NOTE: This function is only called on regular files. There are further * restrictions to the setting of DELETED_FE on directory records * undergoing a flush due to sensitive inode link count calculations. */ static int hammer_frontend_trunc_callback(hammer_record_t record, void *data __unused) { if (record->flags & HAMMER_RECF_DELETED_FE) return(0); #if 0 if (record->flush_state == HAMMER_FST_FLUSH) return(0); #endif hammer_ref(&record->lock); while (record->flags & HAMMER_RECF_INTERLOCK_BE) hammer_wait_mem_record_ident(record, "hmmtrr"); record->flags |= HAMMER_RECF_DELETED_FE; hammer_rel_mem_record(record); return(0); } /* * Return 1 if the caller must check for and delete existing records * before writing out a new data record. * * Return 0 if the caller can just insert the record into the B-Tree without * checking. */ static int hammer_record_needs_overwrite_delete(hammer_record_t record) { hammer_inode_t ip = record->ip; int64_t file_offset; int r; if (ip->ino_data.obj_type == HAMMER_OBJTYPE_DBFILE) file_offset = record->leaf.base.key; else file_offset = record->leaf.base.key - record->leaf.data_len; r = (file_offset < ip->save_trunc_off); if (ip->ino_data.obj_type == HAMMER_OBJTYPE_DBFILE) { if (ip->save_trunc_off <= record->leaf.base.key) ip->save_trunc_off = record->leaf.base.key + 1; } else { if (ip->save_trunc_off < record->leaf.base.key) ip->save_trunc_off = record->leaf.base.key; } return(r); } /* * Backend code. Sync a record to the media. */ int hammer_ip_sync_record_cursor(hammer_cursor_t cursor, hammer_record_t record) { hammer_transaction_t trans = cursor->trans; hammer_mount_t hmp = trans->hmp; int64_t file_offset; int bytes; void *bdata; int error; int doprop; KKASSERT(record->flush_state == HAMMER_FST_FLUSH); KKASSERT(record->flags & HAMMER_RECF_INTERLOCK_BE); KKASSERT(record->leaf.base.localization != 0); /* * Any direct-write related to the record must complete before we * can sync the record to the on-disk media. */ if (record->gflags & (HAMMER_RECG_DIRECT_IO | HAMMER_RECG_DIRECT_INVAL)) hammer_io_direct_wait(record); /* * If this is a bulk-data record placemarker there may be an existing * record on-disk, indicating a data overwrite. If there is the * on-disk record must be deleted before we can insert our new record. * * We've synthesized this record and do not know what the create_tid * on-disk is, nor how much data it represents. * * Keep in mind that (key) for data records is (base_offset + len), * not (base_offset). Also, we only want to get rid of on-disk * records since we are trying to sync our in-memory record, call * hammer_ip_delete_range() with truncating set to 1 to make sure * it skips in-memory records. * * It is ok for the lookup to return ENOENT. * * NOTE OPTIMIZATION: sync_trunc_off is used to determine if we have * to call hammer_ip_delete_range() or not. This also means we must * update sync_trunc_off() as we write. */ if (record->type == HAMMER_MEM_RECORD_DATA && hammer_record_needs_overwrite_delete(record)) { file_offset = record->leaf.base.key - record->leaf.data_len; bytes = HAMMER_BUFSIZE_DOALIGN(record->leaf.data_len); KKASSERT((file_offset & HAMMER_BUFMASK) == 0); error = hammer_ip_delete_range( cursor, record->ip, file_offset, file_offset + bytes - 1, 1); if (error && error != ENOENT) goto done; } /* * If this is a general record there may be an on-disk version * that must be deleted before we can insert the new record. */ if (record->type == HAMMER_MEM_RECORD_GENERAL) { error = hammer_delete_general(cursor, record->ip, &record->leaf); if (error && error != ENOENT) goto done; } /* * Setup the cursor. */ hammer_normalize_cursor(cursor); cursor->key_beg = record->leaf.base; cursor->flags &= ~HAMMER_CURSOR_INITMASK; cursor->flags |= HAMMER_CURSOR_BACKEND; cursor->flags &= ~HAMMER_CURSOR_INSERT; /* * Records can wind up on-media before the inode itself is on-media. * Flag the case. */ record->ip->flags |= HAMMER_INODE_DONDISK; /* * If we are deleting a directory entry an exact match must be * found on-disk. */ if (record->type == HAMMER_MEM_RECORD_DEL) { error = hammer_btree_lookup(cursor); if (error == 0) { KKASSERT(cursor->iprec == NULL); error = hammer_ip_delete_record(cursor, record->ip, trans->tid); if (error == 0) { record->flags |= HAMMER_RECF_DELETED_BE | HAMMER_RECF_COMMITTED; ++record->ip->rec_generation; } } goto done; } /* * We are inserting. * * Issue a lookup to position the cursor and locate the insertion * point. The target key should not exist. If we are creating a * directory entry we may have to iterate the low 32 bits of the * key to find an unused key. */ hammer_sync_lock_sh(trans); cursor->flags |= HAMMER_CURSOR_INSERT; error = hammer_btree_lookup(cursor); if (hammer_debug_inode) hdkprintf("DOINSERT LOOKUP %d\n", error); if (error == 0) { hdkprintf("duplicate rec at (%016jx)\n", (intmax_t)record->leaf.base.key); if (hammer_debug_critical) Debugger("duplicate record1"); error = EIO; } if (error != ENOENT) goto done_unlock; /* * Allocate the record and data. The result buffers will be * marked as being modified and further calls to * hammer_modify_buffer() will result in unneeded UNDO records. * * Support zero-fill records (data == NULL and data_len != 0) */ if (record->type == HAMMER_MEM_RECORD_DATA) { /* * The data portion of a bulk-data record has already been * committed to disk, we need only adjust the layer2 * statistics in the same transaction as our B-Tree insert. */ KKASSERT(record->leaf.data_offset != 0); error = hammer_blockmap_finalize(trans, record->resv, record->leaf.data_offset, record->leaf.data_len); } else if (record->data && record->leaf.data_len) { /* * Wholely cached record, with data. Allocate the data. */ bdata = hammer_alloc_data(trans, record->leaf.data_len, record->leaf.base.rec_type, &record->leaf.data_offset, &cursor->data_buffer, 0, &error); if (bdata == NULL) goto done_unlock; hammer_crc_set_leaf(hmp->version, record->data, &record->leaf); hammer_modify_buffer_noundo(trans, cursor->data_buffer); bcopy(record->data, bdata, record->leaf.data_len); hammer_modify_buffer_done(cursor->data_buffer); } else { /* * Wholely cached record, without data. */ record->leaf.data_offset = 0; record->leaf.data_crc = 0; } error = hammer_btree_insert(cursor, &record->leaf, &doprop); if (hammer_debug_inode && error) { hdkprintf("BTREE INSERT error %d @ %016jx:%d key %016jx\n", error, (intmax_t)cursor->node->node_offset, cursor->index, (intmax_t)record->leaf.base.key); } /* * Our record is on-disk and we normally mark the in-memory version * as having been committed (and not BE-deleted). * * If the record represented a directory deletion but we had to * sync a valid directory entry to disk due to dependancies, * we must convert the record to a covering delete so the * frontend does not have visibility on the synced entry. * * WARNING: cursor's leaf pointer may have changed after do_propagation * returns! */ if (error == 0) { if (doprop) { hammer_btree_do_propagation(cursor, &record->leaf); } if (record->flags & HAMMER_RECF_CONVERT_DELETE) { /* * Must convert deleted directory entry add * to a directory entry delete. */ KKASSERT(record->type == HAMMER_MEM_RECORD_ADD); record->flags &= ~HAMMER_RECF_DELETED_FE; record->type = HAMMER_MEM_RECORD_DEL; KKASSERT(record->ip->obj_id == record->leaf.base.obj_id); KKASSERT(record->flush_state == HAMMER_FST_FLUSH); record->flags &= ~HAMMER_RECF_CONVERT_DELETE; KKASSERT((record->flags & (HAMMER_RECF_COMMITTED | HAMMER_RECF_DELETED_BE)) == 0); /* converted record is not yet committed */ /* hammer_flush_record_done takes care of the rest */ } else { /* * Everything went fine and we are now done with * this record. */ record->flags |= HAMMER_RECF_COMMITTED; ++record->ip->rec_generation; } } else { if (record->leaf.data_offset) { hammer_blockmap_free(trans, record->leaf.data_offset, record->leaf.data_len); } } done_unlock: hammer_sync_unlock(trans); done: return(error); } /* * Add the record to the inode's rec_tree. The low 32 bits of a directory * entry's key is used to deal with hash collisions in the upper 32 bits. * A unique 64 bit key is generated in-memory and may be regenerated a * second time when the directory record is flushed to the on-disk B-Tree. * * A referenced record is passed to this function. This function * eats the reference. If an error occurs the record will be deleted. * * A copy of the temporary record->data pointer provided by the caller * will be made. */ int hammer_mem_add(hammer_record_t record) { hammer_mount_t hmp = record->ip->hmp; /* * Make a private copy of record->data */ if (record->data) KKASSERT(record->flags & HAMMER_RECF_ALLOCDATA); /* * Insert into the RB tree. A unique key should have already * been selected if this is a directory entry. */ if (RB_INSERT(hammer_rec_rb_tree, &record->ip->rec_tree, record)) { record->flags |= HAMMER_RECF_DELETED_FE; hammer_rel_mem_record(record); return (EEXIST); } ++hmp->rsv_recs; ++record->ip->rsv_recs; record->ip->hmp->rsv_databytes += record->leaf.data_len; record->flags |= HAMMER_RECF_ONRBTREE; hammer_modify_inode(NULL, record->ip, HAMMER_INODE_XDIRTY); hammer_rel_mem_record(record); return(0); } /************************************************************************ * HAMMER INODE MERGED-RECORD FUNCTIONS * ************************************************************************ * * These functions augment the B-Tree scanning functions in hammer_btree.c * by merging in-memory records with on-disk records. */ /* * Locate a particular record either in-memory or on-disk. * * NOTE: This is basically a standalone routine, hammer_ip_next() may * NOT be called to iterate results. */ int hammer_ip_lookup(hammer_cursor_t cursor) { int error; /* * If the element is in-memory return it without searching the * on-disk B-Tree */ KKASSERT(cursor->ip); error = hammer_mem_lookup(cursor); if (error == 0) { cursor->leaf = &cursor->iprec->leaf; return(error); } if (error != ENOENT) return(error); /* * If the inode has on-disk components search the on-disk B-Tree. */ if ((cursor->ip->flags & (HAMMER_INODE_ONDISK|HAMMER_INODE_DONDISK)) == 0) return(error); error = hammer_btree_lookup(cursor); if (error == 0) error = hammer_btree_extract_leaf(cursor); return(error); } /* * Helper for hammer_ip_first()/hammer_ip_next() * * NOTE: Both ATEDISK and DISKEOF will be set the same. This sets up * hammer_ip_first() for calling hammer_ip_next(), and sets up the re-seek * state if hammer_ip_next() needs to re-seek. */ static __inline int _hammer_ip_seek_btree(hammer_cursor_t cursor) { hammer_inode_t ip = cursor->ip; int error; if (ip->flags & (HAMMER_INODE_ONDISK|HAMMER_INODE_DONDISK)) { error = hammer_btree_lookup(cursor); if (error == ENOENT || error == EDEADLK) { if (hammer_debug_general & 0x2000) { hdkprintf("error %d node %p %016jx index %d\n", error, cursor->node, (intmax_t)cursor->node->node_offset, cursor->index); } cursor->flags &= ~HAMMER_CURSOR_ATEDISK; error = hammer_btree_iterate(cursor); } if (error == 0) { cursor->flags &= ~(HAMMER_CURSOR_DISKEOF | HAMMER_CURSOR_ATEDISK); } else { cursor->flags |= HAMMER_CURSOR_DISKEOF | HAMMER_CURSOR_ATEDISK; if (error == ENOENT) error = 0; } } else { cursor->flags |= HAMMER_CURSOR_DISKEOF | HAMMER_CURSOR_ATEDISK; error = 0; } return(error); } /* * Helper for hammer_ip_next() * * The caller has determined that the media cursor is further along than the * memory cursor and must be reseeked after a generation number change. */ static int _hammer_ip_reseek(hammer_cursor_t cursor) { struct hammer_base_elm save; hammer_btree_elm_t elm; int error __debugvar; int r; int again = 0; /* * Do the re-seek. */ hkprintf("Debug: re-seeked during scan @ino=%016jx\n", (intmax_t)cursor->ip->obj_id); save = cursor->key_beg; cursor->key_beg = cursor->iprec->leaf.base; error = _hammer_ip_seek_btree(cursor); KKASSERT(error == 0); cursor->key_beg = save; /* * If the memory record was previous returned to * the caller and the media record matches * (-1/+1: only create_tid differs), then iterate * the media record to avoid a double result. */ if ((cursor->flags & HAMMER_CURSOR_ATEDISK) == 0 && (cursor->flags & HAMMER_CURSOR_LASTWASMEM)) { elm = &cursor->node->ondisk->elms[cursor->index]; r = hammer_btree_cmp(&elm->base, &cursor->iprec->leaf.base); if (cursor->flags & HAMMER_CURSOR_ASOF) { if (r >= -1 && r <= 1) { hkprintf("Debug: iterated after " "re-seek (asof r=%d)\n", r); cursor->flags |= HAMMER_CURSOR_ATEDISK; again = 1; } } else { if (r == 0) { hkprintf("Debug: iterated after " "re-seek\n"); cursor->flags |= HAMMER_CURSOR_ATEDISK; again = 1; } } } return(again); } /* * Locate the first record within the cursor's key_beg/key_end range, * restricted to a particular inode. 0 is returned on success, ENOENT * if no records matched the requested range, or some other error. * * When 0 is returned hammer_ip_next() may be used to iterate additional * records within the requested range. * * This function can return EDEADLK, requiring the caller to terminate * the cursor and try again. */ int hammer_ip_first(hammer_cursor_t cursor) { hammer_inode_t ip __debugvar = cursor->ip; int error; KKASSERT(ip != NULL); /* * Clean up fields and setup for merged scan */ cursor->flags &= ~HAMMER_CURSOR_RETEST; /* * Search the in-memory record list (Red-Black tree). Unlike the * B-Tree search, mem_first checks for records in the range. * * This function will setup both ATEMEM and MEMEOF properly for * the ip iteration. ATEMEM will be set if MEMEOF is set. */ hammer_mem_first(cursor); /* * Detect generation changes during blockages, including * blockages which occur on the initial btree search. */ cursor->rec_generation = cursor->ip->rec_generation; /* * Initial search and result */ error = _hammer_ip_seek_btree(cursor); if (error == 0) error = hammer_ip_next(cursor); return (error); } /* * Retrieve the next record in a merged iteration within the bounds of the * cursor. This call may be made multiple times after the cursor has been * initially searched with hammer_ip_first(). * * There are numerous special cases in this code to deal with races between * in-memory records and on-media records. * * 0 is returned on success, ENOENT if no further records match the * requested range, or some other error code is returned. */ int hammer_ip_next(hammer_cursor_t cursor) { hammer_btree_elm_t elm; hammer_record_t rec; hammer_record_t tmprec; int error; int r; again: /* * Get the next on-disk record * * NOTE: If we deleted the last on-disk record we had scanned * ATEDISK will be clear and RETEST will be set, forcing * a call to iterate. The fact that ATEDISK is clear causes * iterate to re-test the 'current' element. If ATEDISK is * set, iterate will skip the 'current' element. */ error = 0; if ((cursor->flags & HAMMER_CURSOR_DISKEOF) == 0) { if (cursor->flags & (HAMMER_CURSOR_ATEDISK | HAMMER_CURSOR_RETEST)) { error = hammer_btree_iterate(cursor); cursor->flags &= ~HAMMER_CURSOR_RETEST; if (error == 0) { cursor->flags &= ~HAMMER_CURSOR_ATEDISK; hammer_cache_node(&cursor->ip->cache[1], cursor->node); } else if (error == ENOENT) { cursor->flags |= HAMMER_CURSOR_DISKEOF | HAMMER_CURSOR_ATEDISK; error = 0; } } } /* * If the generation changed the backend has deleted or committed * one or more memory records since our last check. * * When this case occurs if the disk cursor is > current memory record * or the disk cursor is at EOF, we must re-seek the disk-cursor. * Since the cursor is ahead it must have not yet been eaten (if * not at eof anyway). (XXX data offset case?) * * NOTE: we are not doing a full check here. That will be handled * later on. * * If we have exhausted all memory records we do not have to do any * further seeks. */ while (cursor->rec_generation != cursor->ip->rec_generation && error == 0) { hkprintf("Debug: generation changed during scan @ino=%016jx\n", (intmax_t)cursor->ip->obj_id); cursor->rec_generation = cursor->ip->rec_generation; if (cursor->flags & HAMMER_CURSOR_MEMEOF) break; if (cursor->flags & HAMMER_CURSOR_DISKEOF) { r = 1; } else { KKASSERT((cursor->flags & HAMMER_CURSOR_ATEDISK) == 0); elm = &cursor->node->ondisk->elms[cursor->index]; r = hammer_btree_cmp(&elm->base, &cursor->iprec->leaf.base); } /* * Do we re-seek the media cursor? */ if (r > 0) { if (_hammer_ip_reseek(cursor)) goto again; } } /* * We can now safely get the next in-memory record. We cannot * block here. * * hammer_rec_scan_cmp: Is the record still in our general range, * (non-inclusive of snapshot exclusions)? * hammer_rec_scan_callback: Is the record in our snapshot? */ tmprec = NULL; if ((cursor->flags & HAMMER_CURSOR_MEMEOF) == 0) { /* * If the current memory record was eaten then get the next * one. Stale records are skipped. */ if (cursor->flags & HAMMER_CURSOR_ATEMEM) { tmprec = cursor->iprec; cursor->iprec = NULL; rec = hammer_rec_rb_tree_RB_NEXT(tmprec); while (rec) { if (hammer_rec_scan_cmp(rec, cursor) != 0) break; if (hammer_rec_scan_callback(rec, cursor) != 0) break; rec = hammer_rec_rb_tree_RB_NEXT(rec); } if (cursor->iprec) { KKASSERT(cursor->iprec == rec); cursor->flags &= ~HAMMER_CURSOR_ATEMEM; } else { cursor->flags |= HAMMER_CURSOR_MEMEOF; } cursor->flags &= ~HAMMER_CURSOR_LASTWASMEM; } } /* * MEMORY RECORD VALIDITY TEST * * (We still can't block, which is why tmprec is being held so * long). * * If the memory record is no longer valid we skip it. It may * have been deleted by the frontend. If it was deleted or * committed by the backend the generation change re-seeked the * disk cursor and the record will be present there. */ if (error == 0 && (cursor->flags & HAMMER_CURSOR_MEMEOF) == 0) { KKASSERT(cursor->iprec); KKASSERT((cursor->flags & HAMMER_CURSOR_ATEMEM) == 0); if (!hammer_ip_iterate_mem_good(cursor, cursor->iprec)) { cursor->flags |= HAMMER_CURSOR_ATEMEM; if (tmprec) hammer_rel_mem_record(tmprec); goto again; } } if (tmprec) hammer_rel_mem_record(tmprec); /* * Extract either the disk or memory record depending on their * relative position. */ error = 0; switch(cursor->flags & (HAMMER_CURSOR_ATEDISK | HAMMER_CURSOR_ATEMEM)) { case 0: /* * Both entries valid. Compare the entries and nominally * return the first one in the sort order. Numerous cases * require special attention, however. */ elm = &cursor->node->ondisk->elms[cursor->index]; r = hammer_btree_cmp(&elm->base, &cursor->iprec->leaf.base); /* * If the two entries differ only by their key (-2/2) or * create_tid (-1/1), and are DATA records, we may have a * nominal match. We have to calculate the base file * offset of the data. */ if (r <= 2 && r >= -2 && r != 0 && cursor->ip->ino_data.obj_type == HAMMER_OBJTYPE_REGFILE && cursor->iprec->type == HAMMER_MEM_RECORD_DATA) { int64_t base1 = elm->leaf.base.key - elm->leaf.data_len; int64_t base2 = cursor->iprec->leaf.base.key - cursor->iprec->leaf.data_len; if (base1 == base2) r = 0; } if (r < 0) { error = hammer_btree_extract_leaf(cursor); cursor->flags |= HAMMER_CURSOR_ATEDISK; cursor->flags &= ~HAMMER_CURSOR_LASTWASMEM; break; } /* * If the entries match exactly the memory entry is either * an on-disk directory entry deletion or a bulk data * overwrite. If it is a directory entry deletion we eat * both entries. * * For the bulk-data overwrite case it is possible to have * visibility into both, which simply means the syncer * hasn't gotten around to doing the delete+insert sequence * on the B-Tree. Use the memory entry and throw away the * on-disk entry. * * If the in-memory record is not either of these we * probably caught the syncer while it was syncing it to * the media. Since we hold a shared lock on the cursor, * the in-memory record had better be marked deleted at * this point. */ if (r == 0) { if (cursor->iprec->type == HAMMER_MEM_RECORD_DEL) { if ((cursor->flags & HAMMER_CURSOR_DELETE_VISIBILITY) == 0) { cursor->flags |= HAMMER_CURSOR_ATEDISK; cursor->flags |= HAMMER_CURSOR_ATEMEM; goto again; } } else if (cursor->iprec->type == HAMMER_MEM_RECORD_DATA) { if ((cursor->flags & HAMMER_CURSOR_DELETE_VISIBILITY) == 0) { cursor->flags |= HAMMER_CURSOR_ATEDISK; } /* fall through to memory entry */ } else { hpanic("duplicate mem/B-Tree entry %p %d %08x", cursor->iprec, cursor->iprec->type, cursor->iprec->flags); cursor->flags |= HAMMER_CURSOR_ATEMEM; goto again; } } /* fall through to the memory entry */ case HAMMER_CURSOR_ATEDISK: /* * Only the memory entry is valid. */ cursor->leaf = &cursor->iprec->leaf; cursor->flags |= HAMMER_CURSOR_ATEMEM; cursor->flags |= HAMMER_CURSOR_LASTWASMEM; /* * If the memory entry is an on-disk deletion we should have * also had found a B-Tree record. If the backend beat us * to it it would have interlocked the cursor and we should * have seen the in-memory record marked DELETED_FE. */ if (cursor->iprec->type == HAMMER_MEM_RECORD_DEL && (cursor->flags & HAMMER_CURSOR_DELETE_VISIBILITY) == 0) { hpanic("del-on-disk with no B-Tree entry iprec %p flags %08x", cursor->iprec, cursor->iprec->flags); } break; case HAMMER_CURSOR_ATEMEM: /* * Only the disk entry is valid */ error = hammer_btree_extract_leaf(cursor); cursor->flags |= HAMMER_CURSOR_ATEDISK; cursor->flags &= ~HAMMER_CURSOR_LASTWASMEM; break; default: /* * Neither entry is valid * * XXX error not set properly */ cursor->flags &= ~HAMMER_CURSOR_LASTWASMEM; cursor->leaf = NULL; error = ENOENT; break; } return(error); } /* * Resolve the cursor->data pointer for the current cursor position in * a merged iteration. */ int hammer_ip_resolve_data(hammer_cursor_t cursor) { hammer_record_t record; int error; if (hammer_cursor_inmem(cursor)) { /* * The data associated with an in-memory record is usually * kmalloced, but reserve-ahead data records will have an * on-disk reference. * * NOTE: Reserve-ahead data records must be handled in the * context of the related high level buffer cache buffer * to interlock against async writes. * * NOTE: We might catch a direct write in-progress, in which * case we must wait for it to complete. The wait * function will also clean out any buffer aliases. * * (In fact, it is possible that the write had not * even started yet). */ record = cursor->iprec; cursor->data = record->data; error = 0; if (cursor->data == NULL) { hammer_io_direct_wait(record); KKASSERT(record->leaf.base.rec_type == HAMMER_RECTYPE_DATA); cursor->data = hammer_bread_ext(cursor->trans->hmp, record->leaf.data_offset, record->leaf.data_len, &error, &cursor->data_buffer); } } else { /* * Loading leaf here isn't necessary if it's guaranteed that * the cursor is at a leaf node (which basically should be) * because hammer_btree_extract_data() does that. */ cursor->leaf = &cursor->node->ondisk->elms[cursor->index].leaf; error = hammer_btree_extract_data(cursor); } return(error); } /* * Backend truncation / record replacement - delete records in range. * * Delete all records within the specified range for inode ip. In-memory * records still associated with the frontend are ignored. * * If truncating is non-zero in-memory records associated with the back-end * are ignored. If truncating is > 1 we can return EWOULDBLOCK. * * NOTES: * * * An unaligned range will cause new records to be added to cover * the edge cases. (XXX not implemented yet). * * * Replacement via reservations (see hammer_ip_sync_record_cursor()) * also do not deal with unaligned ranges. * * * ran_end is inclusive (e.g. 0,1023 instead of 0,1024). * * * Record keys for regular file data have to be special-cased since * they indicate the end of the range (key = base + bytes). * * * This function may be asked to delete ridiculously huge ranges, for * example if someone truncates or removes a 1TB regular file. We * must be very careful on restarts and we may have to stop w/ * EWOULDBLOCK to avoid blowing out the buffer cache. */ int hammer_ip_delete_range(hammer_cursor_t cursor, hammer_inode_t ip, int64_t ran_beg, int64_t ran_end, int truncating) { hammer_transaction_t trans = cursor->trans; hammer_btree_leaf_elm_t leaf; int error; int64_t off; int64_t tmp64; KKASSERT(trans->type == HAMMER_TRANS_FLS); retry: hammer_normalize_cursor(cursor); cursor->key_beg.localization = ip->obj_localization | HAMMER_LOCALIZE_MISC; cursor->key_beg.obj_id = ip->obj_id; cursor->key_beg.create_tid = 0; cursor->key_beg.delete_tid = 0; cursor->key_beg.obj_type = 0; if (ip->ino_data.obj_type == HAMMER_OBJTYPE_DBFILE) { cursor->key_beg.key = ran_beg; cursor->key_beg.rec_type = HAMMER_RECTYPE_DB; } else { /* * The key in the B-Tree is (base+bytes), so the first possible * matching key is ran_beg + 1. */ cursor->key_beg.key = ran_beg + 1; cursor->key_beg.rec_type = HAMMER_RECTYPE_DATA; } cursor->key_end = cursor->key_beg; if (ip->ino_data.obj_type == HAMMER_OBJTYPE_DBFILE) { cursor->key_end.key = ran_end; } else { tmp64 = ran_end + MAXPHYS + 1; /* work around GCC-4 bug */ if (tmp64 < ran_end) cursor->key_end.key = HAMMER_MAX_KEY; else cursor->key_end.key = ran_end + MAXPHYS + 1; } cursor->asof = ip->obj_asof; cursor->flags &= ~HAMMER_CURSOR_INITMASK; cursor->flags |= HAMMER_CURSOR_ASOF; cursor->flags |= HAMMER_CURSOR_DELETE_VISIBILITY; cursor->flags |= HAMMER_CURSOR_BACKEND; cursor->flags |= HAMMER_CURSOR_END_INCLUSIVE; error = hammer_ip_first(cursor); /* * Iterate through matching records and mark them as deleted. */ while (error == 0) { leaf = cursor->leaf; KKASSERT(leaf->base.delete_tid == 0); KKASSERT(leaf->base.obj_id == ip->obj_id); /* * There may be overlap cases for regular file data. Also * remember the key for a regular file record is (base + len), * NOT (base). * * Note that due to duplicates (mem & media) allowed by * DELETE_VISIBILITY, off can wind up less then ran_beg. */ if (leaf->base.rec_type == HAMMER_RECTYPE_DATA) { off = leaf->base.key - leaf->data_len; /* * Check the left edge case. We currently do not * split existing records. */ if (off < ran_beg && leaf->base.key > ran_beg) { hpanic("hammer left edge case %016jx %d", (intmax_t)leaf->base.key, leaf->data_len); } /* * Check the right edge case. Note that the * record can be completely out of bounds, which * terminates the search. * * base->key is exclusive of the right edge while * ran_end is inclusive of the right edge. The * (key - data_len) left boundary is inclusive. * * XXX theory-check this test at some point, are * we missing a + 1 somewhere? Note that ran_end * could overflow. */ if (leaf->base.key - 1 > ran_end) { if (leaf->base.key - leaf->data_len > ran_end) break; hpanic("hammer right edge case"); } } else { off = leaf->base.key; } /* * Delete the record. When truncating we do not delete * in-memory (data) records because they represent data * written after the truncation. * * This will also physically destroy the B-Tree entry and * data if the retention policy dictates. The function * will set HAMMER_CURSOR_RETEST to cause hammer_ip_next() * to retest the new 'current' element. */ if (truncating == 0 || hammer_cursor_ondisk(cursor)) { error = hammer_ip_delete_record(cursor, ip, trans->tid); /* * If we have built up too many meta-buffers we risk * deadlocking the kernel and must stop. This can * occur when deleting ridiculously huge files. * sync_trunc_off is updated so the next cycle does * not re-iterate records we have already deleted. * * This is only done with formal truncations. */ if (truncating > 1 && error == 0 && hammer_flusher_meta_limit(ip->hmp)) { ip->sync_trunc_off = off; error = EWOULDBLOCK; } } if (error) break; ran_beg = off; /* for restart */ error = hammer_ip_next(cursor); } if (cursor->node) hammer_cache_node(&ip->cache[1], cursor->node); if (error == EDEADLK) { hammer_done_cursor(cursor); error = hammer_init_cursor(trans, cursor, &ip->cache[1], ip); if (error == 0) goto retry; } if (error == ENOENT) error = 0; return(error); } /* * This backend function deletes the specified record on-disk, similar to * delete_range but for a specific record. Unlike the exact deletions * used when deleting a directory entry this function uses an ASOF search * like delete_range. * * This function may be called with ip->obj_asof set for a slave snapshot, * so don't use it. We always delete non-historical records only. */ static int hammer_delete_general(hammer_cursor_t cursor, hammer_inode_t ip, hammer_btree_leaf_elm_t leaf) { hammer_transaction_t trans = cursor->trans; int error; KKASSERT(trans->type == HAMMER_TRANS_FLS); retry: hammer_normalize_cursor(cursor); cursor->key_beg = leaf->base; cursor->asof = HAMMER_MAX_TID; cursor->flags &= ~HAMMER_CURSOR_INITMASK; cursor->flags |= HAMMER_CURSOR_ASOF; cursor->flags |= HAMMER_CURSOR_BACKEND; cursor->flags &= ~HAMMER_CURSOR_INSERT; error = hammer_btree_lookup(cursor); if (error == 0) { error = hammer_ip_delete_record(cursor, ip, trans->tid); } if (error == EDEADLK) { hammer_done_cursor(cursor); error = hammer_init_cursor(trans, cursor, &ip->cache[1], ip); if (error == 0) goto retry; } return(error); } /* * This function deletes remaining auxillary records when an inode is * being deleted. This function explicitly does not delete the * inode record, directory entry, data, or db records. Those must be * properly disposed of prior to this call. */ int hammer_ip_delete_clean(hammer_cursor_t cursor, hammer_inode_t ip, int *countp) { hammer_transaction_t trans = cursor->trans; hammer_btree_leaf_elm_t leaf __debugvar; int error; KKASSERT(trans->type == HAMMER_TRANS_FLS); retry: hammer_normalize_cursor(cursor); cursor->key_beg.localization = ip->obj_localization | HAMMER_LOCALIZE_MISC; cursor->key_beg.obj_id = ip->obj_id; cursor->key_beg.create_tid = 0; cursor->key_beg.delete_tid = 0; cursor->key_beg.obj_type = 0; cursor->key_beg.rec_type = HAMMER_RECTYPE_CLEAN_START; cursor->key_beg.key = HAMMER_MIN_KEY; cursor->key_end = cursor->key_beg; cursor->key_end.rec_type = HAMMER_RECTYPE_MAX; cursor->key_end.key = HAMMER_MAX_KEY; cursor->asof = ip->obj_asof; cursor->flags &= ~HAMMER_CURSOR_INITMASK; cursor->flags |= HAMMER_CURSOR_END_INCLUSIVE | HAMMER_CURSOR_ASOF; cursor->flags |= HAMMER_CURSOR_DELETE_VISIBILITY; cursor->flags |= HAMMER_CURSOR_BACKEND; error = hammer_ip_first(cursor); /* * Iterate through matching records and mark them as deleted. */ while (error == 0) { leaf = cursor->leaf; KKASSERT(leaf->base.delete_tid == 0); /* * Mark the record and B-Tree entry as deleted. This will * also physically delete the B-Tree entry, record, and * data if the retention policy dictates. The function * will set HAMMER_CURSOR_RETEST to cause hammer_ip_next() * to retest the new 'current' element. * * Directory entries (and delete-on-disk directory entries) * must be synced and cannot be deleted. */ error = hammer_ip_delete_record(cursor, ip, trans->tid); ++*countp; if (error) break; error = hammer_ip_next(cursor); } if (cursor->node) hammer_cache_node(&ip->cache[1], cursor->node); if (error == EDEADLK) { hammer_done_cursor(cursor); error = hammer_init_cursor(trans, cursor, &ip->cache[1], ip); if (error == 0) goto retry; } if (error == ENOENT) error = 0; return(error); } /* * Delete the record at the current cursor. On success the cursor will * be positioned appropriately for an iteration but may no longer be at * a leaf node. * * This routine is only called from the backend. * * NOTE: This can return EDEADLK, requiring the caller to terminate the * cursor and retry. */ int hammer_ip_delete_record(hammer_cursor_t cursor, hammer_inode_t ip, hammer_tid_t tid) { hammer_record_t iprec; int error; KKASSERT(cursor->flags & HAMMER_CURSOR_BACKEND); KKASSERT(tid != 0); /* * In-memory (unsynchronized) records can simply be freed. This * only occurs in range iterations since all other records are * individually synchronized. Thus there should be no confusion with * the interlock. * * An in-memory record may be deleted before being committed to disk, * but could have been accessed in the mean time. The reservation * code will deal with the case. */ if (hammer_cursor_inmem(cursor)) { iprec = cursor->iprec; KKASSERT((iprec->flags & HAMMER_RECF_INTERLOCK_BE) ==0); iprec->flags |= HAMMER_RECF_DELETED_FE; iprec->flags |= HAMMER_RECF_DELETED_BE; KKASSERT(iprec->ip == ip); ++ip->rec_generation; return(0); } /* * On-disk records are marked as deleted by updating their delete_tid. * This does not effect their position in the B-Tree (which is based * on their create_tid). * * Frontend B-Tree operations track inodes so we tell * hammer_delete_at_cursor() not to. */ error = hammer_btree_extract_leaf(cursor); if (error == 0) { error = hammer_delete_at_cursor( cursor, HAMMER_DELETE_ADJUST | hammer_nohistory(ip), cursor->trans->tid, cursor->trans->time32, 0, NULL); } return(error); } /* * Used to write a generic record w/optional data to the media b-tree * when no inode context is available. Used by the mirroring and * snapshot code. * * Caller must set cursor->key_beg to leaf->base. The cursor must be * flagged for backend operation and not flagged ASOF (since we are * doing an insertion). * * This function will acquire the appropriate sync lock and will set * the cursor insertion flag for the operation, do the btree lookup, * and the insertion, and clear the insertion flag and sync lock before * returning. The cursor state will be such that the caller can continue * scanning (used by the mirroring code). * * mode: HAMMER_CREATE_MODE_UMIRROR copyin data, check crc * HAMMER_CREATE_MODE_SYS bcopy data, generate crc * * NOTE: EDEADLK can be returned. The caller must do deadlock handling and * retry. * * EALREADY can be returned if the record already exists (WARNING, * because ASOF cannot be used no check is made for illegal * duplicates). * * NOTE: Do not use the function for normal inode-related records as this * functions goes directly to the media and is not integrated with * in-memory records. */ int hammer_create_at_cursor(hammer_cursor_t cursor, hammer_btree_leaf_elm_t leaf, void *udata, int mode) { hammer_transaction_t trans; hammer_mount_t hmp; hammer_buffer_t data_buffer; hammer_off_t ndata_offset; hammer_tid_t high_tid; void *ndata; int error; int doprop; trans = cursor->trans; hmp = trans->hmp; data_buffer = NULL; ndata_offset = 0; doprop = 0; KKASSERT((cursor->flags & (HAMMER_CURSOR_BACKEND | HAMMER_CURSOR_ASOF)) == (HAMMER_CURSOR_BACKEND)); hammer_sync_lock_sh(trans); if (leaf->data_len) { ndata = hammer_alloc_data(trans, leaf->data_len, leaf->base.rec_type, &ndata_offset, &data_buffer, 0, &error); if (ndata == NULL) { hammer_sync_unlock(trans); return (error); } leaf->data_offset = ndata_offset; hammer_modify_buffer_noundo(trans, data_buffer); switch(mode) { case HAMMER_CREATE_MODE_UMIRROR: error = copyin(udata, ndata, leaf->data_len); if (error == 0) { if (hammer_crc_test_leaf(hmp->version, ndata, leaf) == 0) { hdkprintf("CRC DATA @ %016jx/%d MISMATCH ON PIPE\n", (intmax_t)ndata_offset, leaf->data_len); error = EINVAL; } else { error = hammer_cursor_localize_data( hmp, ndata, leaf); } } break; case HAMMER_CREATE_MODE_SYS: bcopy(udata, ndata, leaf->data_len); error = 0; hammer_crc_set_leaf(hmp->version, ndata, leaf); break; default: hpanic("bad mode %d", mode); break; /* NOT REACHED */ } hammer_modify_buffer_done(data_buffer); } else { leaf->data_offset = 0; error = 0; ndata = NULL; } if (error) goto failed; /* * Do the insertion. This can fail with a EDEADLK or EALREADY */ cursor->flags |= HAMMER_CURSOR_INSERT; error = hammer_btree_lookup(cursor); if (error != ENOENT) { if (error == 0) error = EALREADY; goto failed; } error = hammer_btree_insert(cursor, leaf, &doprop); /* * Cursor is left on current element, we want to skip it now. * (in case the caller is scanning) */ cursor->flags |= HAMMER_CURSOR_ATEDISK; cursor->flags &= ~HAMMER_CURSOR_INSERT; /* * If the insertion happens to be creating (and not just replacing) * an inode we have to track it. */ if (error == 0 && leaf->base.rec_type == HAMMER_RECTYPE_INODE && leaf->base.delete_tid == 0) { hammer_modify_volume_field(trans, trans->rootvol, vol0_stat_inodes); ++trans->hmp->rootvol->ondisk->vol0_stat_inodes; hammer_modify_volume_done(trans->rootvol); } /* * vol0_next_tid must track the highest TID stored in the filesystem. * We do not need to generate undo for this update. */ high_tid = leaf->base.create_tid; if (high_tid < leaf->base.delete_tid) high_tid = leaf->base.delete_tid; if (trans->rootvol->ondisk->vol0_next_tid < high_tid) { hammer_modify_volume_noundo(trans, trans->rootvol); trans->rootvol->ondisk->vol0_next_tid = high_tid; hammer_modify_volume_done(trans->rootvol); } /* * WARNING! cursor's leaf pointer may have changed after * do_propagation returns. */ if (error == 0 && doprop) hammer_btree_do_propagation(cursor, leaf); failed: /* * Cleanup */ if (error && leaf->data_offset) { hammer_blockmap_free(trans, leaf->data_offset, leaf->data_len); } hammer_sync_unlock(trans); if (data_buffer) hammer_rel_buffer(data_buffer, 0); return (error); } /* * Delete the B-Tree element at the current cursor and do any necessary * mirror propagation. * * The cursor must be properly positioned for an iteration on return but * may be pointing at an internal element. * * An element can be un-deleted by passing a delete_tid of 0 with * HAMMER_DELETE_ADJUST. * * This function will store the number of bytes deleted in *stat_bytes * if stat_bytes is not NULL. */ int hammer_delete_at_cursor(hammer_cursor_t cursor, int delete_flags, hammer_tid_t delete_tid, uint32_t delete_ts, int track, int64_t *stat_bytes) { struct hammer_btree_leaf_elm save_leaf; hammer_transaction_t trans; hammer_btree_leaf_elm_t leaf; hammer_node_t node; hammer_btree_elm_t elm; hammer_off_t data_offset; int32_t data_len; int64_t bytes; int ndelete; int error; int icount; int doprop; error = hammer_cursor_upgrade(cursor); if (error) return(error); trans = cursor->trans; node = cursor->node; elm = &node->ondisk->elms[cursor->index]; leaf = &elm->leaf; KKASSERT(elm->base.btype == HAMMER_BTREE_TYPE_RECORD); hammer_sync_lock_sh(trans); bytes = 0; doprop = 0; icount = 0; /* * Adjust the delete_tid. Update the mirror_tid propagation field * as well. delete_tid can be 0 (undelete -- used by mirroring). */ if (delete_flags & HAMMER_DELETE_ADJUST) { if (elm->base.rec_type == HAMMER_RECTYPE_INODE) { if (elm->leaf.base.delete_tid == 0 && delete_tid) icount = -1; if (elm->leaf.base.delete_tid && delete_tid == 0) icount = 1; } hammer_modify_node(trans, node, elm, sizeof(*elm)); elm->leaf.base.delete_tid = delete_tid; elm->leaf.delete_ts = delete_ts; hammer_modify_node_done(node); if (elm->leaf.base.delete_tid > node->ondisk->mirror_tid) { hammer_modify_node_field(trans, node, mirror_tid); node->ondisk->mirror_tid = elm->leaf.base.delete_tid; hammer_modify_node_done(node); doprop = 1; if (hammer_debug_general & 0x0002) { hdkprintf("propagate %016jx @%016jx\n", (intmax_t)elm->leaf.base.delete_tid, (intmax_t)node->node_offset); } } /* * Adjust for the iteration. We have deleted the current * element and want to clear ATEDISK so the iteration does * not skip the element after, which now becomes the current * element. This element must be re-tested if doing an * iteration, which is handled by the RETEST flag. */ if ((cursor->flags & HAMMER_CURSOR_DISKEOF) == 0) { cursor->flags |= HAMMER_CURSOR_RETEST; cursor->flags &= ~HAMMER_CURSOR_ATEDISK; } /* * An on-disk record cannot have the same delete_tid * as its create_tid. In a chain of record updates * this could result in a duplicate record. */ KKASSERT(elm->leaf.base.delete_tid != elm->leaf.base.create_tid); } /* * Destroy the B-Tree element if asked (typically if a nohistory * file or mount, or when called by the pruning code). * * Adjust the ATEDISK flag to properly support iterations. */ if (delete_flags & HAMMER_DELETE_DESTROY) { data_offset = elm->leaf.data_offset; data_len = elm->leaf.data_len; if (doprop) { save_leaf = elm->leaf; leaf = &save_leaf; } if (elm->base.rec_type == HAMMER_RECTYPE_INODE && elm->leaf.base.delete_tid == 0) { icount = -1; } error = hammer_btree_delete(cursor, &ndelete); if (error == 0) { /* * The deletion moves the next element (if any) to * the current element position. We must clear * ATEDISK so this element is not skipped and we * must set RETEST to force any iteration to re-test * the element. */ if ((cursor->flags & HAMMER_CURSOR_DISKEOF) == 0) { cursor->flags |= HAMMER_CURSOR_RETEST; cursor->flags &= ~HAMMER_CURSOR_ATEDISK; } bytes += (ndelete * sizeof(struct hammer_node_ondisk)); switch(HAMMER_ZONE(data_offset)) { case HAMMER_ZONE_LARGE_DATA: case HAMMER_ZONE_SMALL_DATA: case HAMMER_ZONE_META: hammer_blockmap_free(trans, data_offset, data_len); bytes += data_len; break; default: break; } } } /* * Track inode count and next_tid. This is used by the mirroring * and PFS code. icount can be negative, zero, or positive. */ if (error == 0 && track) { if (icount) { hammer_modify_volume_field(trans, trans->rootvol, vol0_stat_inodes); trans->rootvol->ondisk->vol0_stat_inodes += icount; hammer_modify_volume_done(trans->rootvol); } if (trans->rootvol->ondisk->vol0_next_tid < delete_tid) { hammer_modify_volume_noundo(trans, trans->rootvol); trans->rootvol->ondisk->vol0_next_tid = delete_tid; hammer_modify_volume_done(trans->rootvol); } } /* * mirror_tid propagation occurs if the node's mirror_tid had to be * updated while adjusting the delete_tid. * * This occurs when deleting even in nohistory mode, but does not * occur when pruning an already-deleted node. * * cursor->ip is NULL when called from the pruning, mirroring, * and pfs code. If non-NULL propagation will be conditionalized * on whether the PFS is in no-history mode or not. * * WARNING: cursor's leaf pointer may have changed after do_propagation * returns! */ if (doprop) { if (cursor->ip) hammer_btree_do_propagation(cursor, leaf); else hammer_btree_do_propagation(cursor, leaf); } if (stat_bytes) *stat_bytes = bytes; hammer_sync_unlock(trans); return (error); } /* * Determine whether we can remove a directory. This routine checks whether * a directory is empty or not and enforces flush connectivity. * * Flush connectivity requires that we block if the target directory is * currently flushing, otherwise it may not end up in the same flush group. * * Returns 0 on success, ENOTEMPTY or EDEADLK (or other errors) on failure. */ int hammer_ip_check_directory_empty(hammer_transaction_t trans, hammer_inode_t ip) { struct hammer_cursor cursor; int error; /* * Check directory empty */ hammer_init_cursor(trans, &cursor, &ip->cache[1], ip); cursor.key_beg.localization = ip->obj_localization | hammer_dir_localization(ip); cursor.key_beg.obj_id = ip->obj_id; cursor.key_beg.create_tid = 0; cursor.key_beg.delete_tid = 0; cursor.key_beg.obj_type = 0; cursor.key_beg.rec_type = HAMMER_RECTYPE_ENTRY_START; cursor.key_beg.key = HAMMER_MIN_KEY; cursor.key_end = cursor.key_beg; cursor.key_end.rec_type = HAMMER_RECTYPE_MAX; cursor.key_end.key = HAMMER_MAX_KEY; cursor.asof = ip->obj_asof; cursor.flags |= HAMMER_CURSOR_END_INCLUSIVE | HAMMER_CURSOR_ASOF; error = hammer_ip_first(&cursor); if (error == ENOENT) error = 0; else if (error == 0) error = ENOTEMPTY; hammer_done_cursor(&cursor); return(error); } /* * Localize the data payload. Directory entries may need their * localization adjusted. */ static int hammer_cursor_localize_data(hammer_mount_t hmp, hammer_data_ondisk_t data, hammer_btree_leaf_elm_t leaf) { uint32_t localization; if (leaf->base.rec_type == HAMMER_RECTYPE_DIRENTRY) { localization = leaf->base.localization & HAMMER_LOCALIZE_PSEUDOFS_MASK; if (data->entry.localization != localization) { data->entry.localization = localization; hammer_crc_set_leaf(hmp->version, data, leaf); } } return(0); } |