summaryrefslogtreecommitdiffstats
path: root/recipes-containers/vcontainer/files/vcontainer-common.sh
blob: 792bd7a2ce5c0a61f8f4395bcced68fecf274e43 (plain)
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
2662
2663
2664
2665
2666
2667
2668
2669
2670
2671
2672
2673
2674
2675
2676
2677
2678
2679
2680
2681
2682
2683
2684
2685
2686
2687
2688
2689
2690
2691
2692
2693
2694
2695
2696
2697
2698
2699
2700
2701
2702
2703
2704
2705
2706
2707
2708
2709
2710
2711
2712
2713
2714
2715
2716
2717
2718
2719
2720
2721
2722
2723
2724
2725
2726
2727
2728
2729
2730
2731
2732
2733
2734
2735
2736
#!/bin/bash
# SPDX-FileCopyrightText: Copyright (C) 2025 Bruce Ashfield
#
# SPDX-License-Identifier: GPL-2.0-only
#
# vcontainer-common.sh: Shared code for vdkr and vpdmn CLI wrappers
#
# This file is sourced by vdkr.sh and vpdmn.sh after they set:
#   VCONTAINER_RUNTIME_NAME   - Tool name (vdkr or vpdmn)
#   VCONTAINER_RUNTIME_CMD    - Container command (docker or podman)
#   VCONTAINER_RUNTIME_PREFIX - Env var prefix (VDKR or VPDMN)
#   VCONTAINER_IMPORT_TARGET  - skopeo target (docker-daemon: or containers-storage:)
#   VCONTAINER_STATE_FILE     - State image name (docker-state.img or podman-state.img)
#   VCONTAINER_OTHER_PREFIX   - Other tool's prefix for orphan checking (VPDMN or VDKR)
#   VCONTAINER_VERSION        - Tool version

set -e

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

# Validate required variables are set
: "${VCONTAINER_RUNTIME_NAME:?VCONTAINER_RUNTIME_NAME must be set}"
: "${VCONTAINER_RUNTIME_CMD:?VCONTAINER_RUNTIME_CMD must be set}"
: "${VCONTAINER_RUNTIME_PREFIX:?VCONTAINER_RUNTIME_PREFIX must be set}"
: "${VCONTAINER_IMPORT_TARGET:?VCONTAINER_IMPORT_TARGET must be set}"
: "${VCONTAINER_STATE_FILE:?VCONTAINER_STATE_FILE must be set}"
: "${VCONTAINER_OTHER_PREFIX:?VCONTAINER_OTHER_PREFIX must be set}"
: "${VCONTAINER_VERSION:?VCONTAINER_VERSION must be set}"

# ============================================================================
# Configuration Management
# ============================================================================
# Config directory can be set via:
#   1. --config-dir command line option
#   2. ${VCONTAINER_RUNTIME_PREFIX}_CONFIG_DIR environment variable
#   3. Default: ~/.config/${VCONTAINER_RUNTIME_NAME}
#
# Config file format: key=value (one per line)
# Supported keys: arch, timeout, state-dir, verbose, idle-timeout, auto-daemon
# ============================================================================

# Pre-parse --config-dir from command line (needs to happen before detect_default_arch)
_preparse_config_dir() {
    local i=1
    while [ $i -le $# ]; do
        local arg="${!i}"
        case "$arg" in
            --config-dir)
                i=$((i + 1))
                echo "${!i}"
                return
                ;;
            --config-dir=*)
                echo "${arg#--config-dir=}"
                return
                ;;
        esac
        i=$((i + 1))
    done
    echo ""
}

_PREPARSE_CONFIG_DIR=$(_preparse_config_dir "$@")

# Get environment variable value dynamically
_get_env_var() {
    local var_name="${VCONTAINER_RUNTIME_PREFIX}_$1"
    echo "${!var_name}"
}

CONFIG_DIR="${_PREPARSE_CONFIG_DIR:-$(_get_env_var CONFIG_DIR)}"
[ -z "$CONFIG_DIR" ] && CONFIG_DIR="$HOME/.config/$VCONTAINER_RUNTIME_NAME"
CONFIG_FILE="$CONFIG_DIR/config"

# Read a config value
# Usage: config_get <key> [default]
config_get() {
    local key="$1"
    local default="$2"

    if [ -f "$CONFIG_FILE" ]; then
        local value=$(grep "^${key}=" "$CONFIG_FILE" 2>/dev/null | cut -d= -f2- | tr -d '[:space:]')
        if [ -n "$value" ]; then
            echo "$value"
            return
        fi
    fi
    echo "$default"
}

# Write a config value
# Usage: config_set <key> <value>
config_set() {
    local key="$1"
    local value="$2"

    mkdir -p "$CONFIG_DIR"

    if [ -f "$CONFIG_FILE" ]; then
        # Remove existing key
        grep -v "^${key}=" "$CONFIG_FILE" > "$CONFIG_FILE.tmp" 2>/dev/null || true
        mv "$CONFIG_FILE.tmp" "$CONFIG_FILE"
    fi

    # Add new value
    echo "${key}=${value}" >> "$CONFIG_FILE"
}

# Remove a config value
# Usage: config_unset <key>
config_unset() {
    local key="$1"

    if [ -f "$CONFIG_FILE" ]; then
        grep -v "^${key}=" "$CONFIG_FILE" > "$CONFIG_FILE.tmp" 2>/dev/null || true
        mv "$CONFIG_FILE.tmp" "$CONFIG_FILE"
    fi
}

# List all config values
config_list() {
    if [ -f "$CONFIG_FILE" ]; then
        cat "$CONFIG_FILE"
    fi
}

# Get config default value
config_default() {
    local key="$1"
    case "$key" in
        arch)         uname -m ;;
        timeout)      echo "300" ;;
        state-dir)    echo "$HOME/.$VCONTAINER_RUNTIME_NAME" ;;
        verbose)      echo "false" ;;
        idle-timeout) echo "1800" ;;   # 30 minutes
        auto-daemon)  echo "true" ;;   # Auto-start daemon by default
        registry)     echo "" ;;       # Default registry for unqualified images
        *)            echo "" ;;
    esac
}

# ============================================================================
# Architecture Detection
# ============================================================================
# Priority order:
# 1. --arch / -a command line flag (parsed below)
# 2. Executable name: ${name}-aarch64 -> aarch64, ${name}-x86_64 -> x86_64
# 3. ${PREFIX}_ARCH environment variable
# 4. Config file: $CONFIG_DIR/config (arch key)
# 5. Legacy config file: $CONFIG_DIR/arch (for backwards compatibility)
# 6. Host architecture (uname -m)
# ============================================================================

detect_arch_from_name() {
    local prog_name=$(basename "$0")
    case "$prog_name" in
        ${VCONTAINER_RUNTIME_NAME}-aarch64) echo "aarch64" ;;
        ${VCONTAINER_RUNTIME_NAME}-x86_64)  echo "x86_64" ;;
        *)                                   echo "" ;;
    esac
}

detect_default_arch() {
    # Check executable name first
    local name_arch=$(detect_arch_from_name)
    if [ -n "$name_arch" ]; then
        echo "$name_arch"
        return
    fi

    # Check environment variable
    local env_arch=$(_get_env_var ARCH)
    if [ -n "$env_arch" ]; then
        echo "$env_arch"
        return
    fi

    # Check new config file (arch key)
    local config_arch=$(config_get "arch" "")
    if [ -n "$config_arch" ]; then
        echo "$config_arch"
        return
    fi

    # Check legacy config file for backwards compatibility
    local legacy_file="$CONFIG_DIR/arch"
    if [ -f "$legacy_file" ]; then
        local legacy_arch=$(cat "$legacy_file" | tr -d '[:space:]')
        if [ -n "$legacy_arch" ]; then
            echo "$legacy_arch"
            return
        fi
    fi

    # Fall back to host architecture
    uname -m
}

DEFAULT_ARCH=$(detect_default_arch)
BLOB_DIR="$(_get_env_var BLOB_DIR)"
VERBOSE="$(_get_env_var VERBOSE)"
[ -z "$VERBOSE" ] && VERBOSE="false"
STATELESS="$(_get_env_var STATELESS)"
[ -z "$STATELESS" ] && STATELESS="false"

# Default state directory (per-architecture)
DEFAULT_STATE_DIR="$(_get_env_var STATE_DIR)"
[ -z "$DEFAULT_STATE_DIR" ] && DEFAULT_STATE_DIR="$HOME/.$VCONTAINER_RUNTIME_NAME"

# Other tool's state directory (for orphan checking)
OTHER_STATE_DIR="$HOME/.$(echo $VCONTAINER_OTHER_PREFIX | tr 'A-Z' 'a-z')"

# Runner script
RUNNER="$(_get_env_var RUNNER)"
[ -z "$RUNNER" ] && RUNNER="$SCRIPT_DIR/vrunner.sh"

# Colors (use $'...' for proper escape interpretation)
RED=$'\033[0;31m'
GREEN=$'\033[0;32m'
YELLOW=$'\033[0;33m'
BLUE=$'\033[0;34m'
CYAN=$'\033[0;36m'
BOLD=$'\033[1m'
NC=$'\033[0m'

# Check OCI image architecture and warn/error if mismatched
# Usage: check_oci_arch <oci_dir> <target_arch>
# Returns: 0 if match or non-OCI, 1 if mismatch
check_oci_arch() {
    local oci_dir="$1"
    local target_arch="$2"

    # Only check OCI directories
    if [ ! -f "$oci_dir/index.json" ]; then
        return 0
    fi

    # Try to extract architecture from the OCI image
    # OCI structure: index.json -> manifest -> config blob -> architecture
    local image_arch=""

    # First, get the manifest digest from index.json
    local manifest_digest=$(cat "$oci_dir/index.json" 2>/dev/null | \
        grep -o '"digest"[[:space:]]*:[[:space:]]*"sha256:[a-f0-9]*"' | head -1 | \
        sed 's/.*sha256:\([a-f0-9]*\)".*/\1/')

    if [ -n "$manifest_digest" ]; then
        local manifest_file="$oci_dir/blobs/sha256/$manifest_digest"
        if [ -f "$manifest_file" ]; then
            # Get the config digest from manifest
            local config_digest=$(cat "$manifest_file" 2>/dev/null | \
                grep -o '"config"[[:space:]]*:[[:space:]]*{[^}]*"digest"[[:space:]]*:[[:space:]]*"sha256:[a-f0-9]*"' | \
                sed 's/.*sha256:\([a-f0-9]*\)".*/\1/')

            if [ -n "$config_digest" ]; then
                local config_file="$oci_dir/blobs/sha256/$config_digest"
                if [ -f "$config_file" ]; then
                    # Extract architecture from config
                    image_arch=$(cat "$config_file" 2>/dev/null | \
                        grep -o '"architecture"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | \
                        sed 's/.*"\([^"]*\)"$/\1/')
                fi
            fi
        fi
    fi

    if [ -z "$image_arch" ]; then
        # Couldn't determine architecture, allow import with warning
        echo -e "${YELLOW}[$VCONTAINER_RUNTIME_NAME]${NC} Warning: Could not determine image architecture" >&2
        return 0
    fi

    # Normalize architecture names
    local normalized_image_arch="$image_arch"
    local normalized_target_arch="$target_arch"

    case "$image_arch" in
        arm64) normalized_image_arch="aarch64" ;;
        amd64) normalized_image_arch="x86_64" ;;
    esac

    case "$target_arch" in
        arm64) normalized_target_arch="aarch64" ;;
        amd64) normalized_target_arch="x86_64" ;;
    esac

    if [ "$normalized_image_arch" != "$normalized_target_arch" ]; then
        echo -e "${RED}[$VCONTAINER_RUNTIME_NAME]${NC} Architecture mismatch!" >&2
        echo -e "${RED}[$VCONTAINER_RUNTIME_NAME]${NC}   Image architecture: ${BOLD}$image_arch${NC}" >&2
        echo -e "${RED}[$VCONTAINER_RUNTIME_NAME]${NC}   Target architecture: ${BOLD}$target_arch${NC}" >&2
        echo -e "${YELLOW}[$VCONTAINER_RUNTIME_NAME]${NC} Use --arch $image_arch to import to a matching environment" >&2
        return 1
    fi

    [ "$VERBOSE" = "true" ] && echo -e "${GREEN}[$VCONTAINER_RUNTIME_NAME]${NC} Image architecture: $image_arch (matches target)" >&2
    return 0
}

# ============================================================================
# Multi-Architecture OCI Support
# ============================================================================
# OCI Image Index (manifest list) format allows multiple platform-specific
# images under a single tag. These functions detect and handle multi-arch OCI.
# ============================================================================

# Normalize architecture name to OCI convention
# Usage: normalize_arch_to_oci <arch>
# Returns: OCI-format architecture (arm64, amd64, etc.)
normalize_arch_to_oci() {
    local arch="$1"
    case "$arch" in
        aarch64) echo "arm64" ;;
        x86_64)  echo "amd64" ;;
        *)       echo "$arch" ;;
    esac
}

# Normalize OCI architecture to Yocto/Linux convention
# Usage: normalize_arch_from_oci <arch>
# Returns: Linux-format architecture (aarch64, x86_64, etc.)
normalize_arch_from_oci() {
    local arch="$1"
    case "$arch" in
        arm64) echo "aarch64" ;;
        amd64) echo "x86_64" ;;
        *)     echo "$arch" ;;
    esac
}

# Check if OCI directory contains a multi-architecture Image Index
# Usage: is_oci_image_index <oci_dir>
# Returns: 0 if multi-arch, 1 if single-arch or not OCI
is_oci_image_index() {
    local oci_dir="$1"

    [ -f "$oci_dir/index.json" ] || return 1

    # Check if index.json has manifests with platform info
    # Multi-arch images have "platform" object in manifest entries
    if grep -q '"platform"' "$oci_dir/index.json" 2>/dev/null; then
        # Also verify there are multiple manifests
        local manifest_count=$(grep -c '"digest"' "$oci_dir/index.json" 2>/dev/null || echo "0")
        [ "$manifest_count" -gt 1 ] && return 0

        # Single manifest with platform info is also a valid Image Index
        # (could be a multi-arch image built with only one arch so far)
        return 0
    fi

    return 1
}

# Get list of available platforms in a multi-arch OCI Image Index
# Usage: get_oci_platforms <oci_dir>
# Returns: Space-separated list of architectures (e.g., "arm64 amd64")
get_oci_platforms() {
    local oci_dir="$1"

    [ -f "$oci_dir/index.json" ] || return 1

    # Extract architecture values from platform objects
    # Format: "platform": { "architecture": "arm64", "os": "linux" }
    grep -o '"architecture"[[:space:]]*:[[:space:]]*"[^"]*"' "$oci_dir/index.json" 2>/dev/null | \
        sed 's/.*"\([^"]*\)"$/\1/' | \
        tr '\n' ' ' | sed 's/ $//'
}

# Select manifest digest for a specific platform from OCI Image Index
# Usage: select_platform_manifest <oci_dir> <target_arch>
# Returns: sha256 digest of the matching manifest (without "sha256:" prefix)
# Sets OCI_SELECTED_PLATFORM to the matched platform for informational purposes
select_platform_manifest() {
    local oci_dir="$1"
    local target_arch="$2"

    [ -f "$oci_dir/index.json" ] || return 1

    # Normalize target arch to OCI convention
    local oci_arch=$(normalize_arch_to_oci "$target_arch")

    # Parse index.json to find manifest with matching platform
    # This is done without jq using grep/sed for portability
    local in_manifest=0
    local current_digest=""
    local current_arch=""
    local matched_digest=""

    # Read index.json line by line
    while IFS= read -r line; do
        # Track when we're inside a manifest entry
        if echo "$line" | grep -q '"manifests"'; then
            in_manifest=1
            continue
        fi

        if [ "$in_manifest" = "1" ]; then
            # Extract digest
            if echo "$line" | grep -q '"digest"'; then
                current_digest=$(echo "$line" | sed 's/.*"sha256:\([a-f0-9]*\)".*/\1/')
            fi

            # Extract architecture from platform
            # Handle both formats: "architecture": "arm64" or {"architecture": "arm64", ...}
            if echo "$line" | grep -q '"architecture"'; then
                current_arch=$(echo "$line" | sed 's/.*"architecture"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/')

                # Check if this matches our target
                if [ "$current_arch" = "$oci_arch" ]; then
                    matched_digest="$current_digest"
                    OCI_SELECTED_PLATFORM="$current_arch"
                    break
                fi
            fi

            # Reset on closing brace (end of manifest entry)
            if echo "$line" | grep -q '^[[:space:]]*}'; then
                current_digest=""
                current_arch=""
            fi
        fi
    done < "$oci_dir/index.json"

    if [ -n "$matched_digest" ]; then
        echo "$matched_digest"
        return 0
    fi

    return 1
}

# Extract a single platform from multi-arch OCI to a new OCI directory
# Usage: extract_platform_oci <src_oci_dir> <dest_oci_dir> <manifest_digest>
# This creates a single-arch OCI directory that skopeo can import
extract_platform_oci() {
    local src_dir="$1"
    local dest_dir="$2"
    local manifest_digest="$3"

    mkdir -p "$dest_dir/blobs/sha256"

    # Copy the manifest blob
    cp "$src_dir/blobs/sha256/$manifest_digest" "$dest_dir/blobs/sha256/"

    # Read the manifest to get config and layer digests
    local manifest_file="$src_dir/blobs/sha256/$manifest_digest"

    # Extract and copy config blob
    local config_digest=$(grep -o '"config"[[:space:]]*:[[:space:]]*{[^}]*"digest"[[:space:]]*:[[:space:]]*"sha256:[a-f0-9]*"' "$manifest_file" | \
        sed 's/.*sha256:\([a-f0-9]*\)".*/\1/')
    if [ -n "$config_digest" ] && [ -f "$src_dir/blobs/sha256/$config_digest" ]; then
        cp "$src_dir/blobs/sha256/$config_digest" "$dest_dir/blobs/sha256/"
    fi

    # Extract and copy layer blobs
    grep -o '"digest"[[:space:]]*:[[:space:]]*"sha256:[a-f0-9]*"' "$manifest_file" | \
        sed 's/.*sha256:\([a-f0-9]*\)".*/\1/' | while read -r layer_digest; do
        if [ -f "$src_dir/blobs/sha256/$layer_digest" ]; then
            cp "$src_dir/blobs/sha256/$layer_digest" "$dest_dir/blobs/sha256/"
        fi
    done

    # Get manifest size
    local manifest_size=$(stat -c%s "$manifest_file" 2>/dev/null || stat -f%z "$manifest_file" 2>/dev/null)

    # Create new index.json pointing to just this manifest
    cat > "$dest_dir/index.json" << EOF
{
  "schemaVersion": 2,
  "manifests": [
    {
      "mediaType": "application/vnd.oci.image.manifest.v1+json",
      "digest": "sha256:$manifest_digest",
      "size": $manifest_size
    }
  ]
}
EOF

    # Copy oci-layout
    if [ -f "$src_dir/oci-layout" ]; then
        cp "$src_dir/oci-layout" "$dest_dir/"
    else
        echo '{"imageLayoutVersion": "1.0.0"}' > "$dest_dir/oci-layout"
    fi

    return 0
}

show_usage() {
    local PROG_NAME=$(basename "$0")
    local RUNTIME_UPPER=$(echo "$VCONTAINER_RUNTIME_CMD" | sed 's/./\U&/')
    cat << EOF
${BOLD}${PROG_NAME}${NC} v$VCONTAINER_VERSION - ${RUNTIME_UPPER} CLI for cross-architecture emulation

${BOLD}USAGE:${NC}
    ${PROG_NAME} [OPTIONS] <command> [args...]

${BOLD}${RUNTIME_UPPER}-COMPATIBLE COMMANDS:${NC}
  ${BOLD}Images:${NC}
    ${CYAN}images${NC}                       List images in emulated ${RUNTIME_UPPER}
    ${CYAN}pull${NC} <image>                 Pull image from registry
    ${CYAN}load${NC} -i <file>               Load ${RUNTIME_UPPER} image archive (${VCONTAINER_RUNTIME_CMD} save output)
    ${CYAN}import${NC} <tarball> [name:tag]  Import rootfs tarball as image
    ${CYAN}save${NC} -o <file> <image>       Save image to tar archive
    ${CYAN}tag${NC} <source> <target>        Tag an image
    ${CYAN}rmi${NC} <image>                  Remove an image
    ${CYAN}history${NC} <image>              Show image layer history
    ${CYAN}inspect${NC} <image|container>    Display detailed info

  ${BOLD}Containers:${NC}
    ${CYAN}run${NC} [opts] <image> [cmd]     Run a command in a new container
    ${CYAN}ps${NC} [options]                 List containers
    ${CYAN}rm${NC} <container>               Remove a container
    ${CYAN}logs${NC} <container>             View container logs
    ${CYAN}start${NC} <container>            Start a stopped container
    ${CYAN}stop${NC} <container>             Stop a running container
    ${CYAN}restart${NC} <container>          Restart a container
    ${CYAN}kill${NC} <container>             Kill a running container
    ${CYAN}pause${NC} <container>            Pause a running container
    ${CYAN}unpause${NC} <container>          Unpause a container
    ${CYAN}commit${NC} <container> <image>   Create image from container
    ${CYAN}exec${NC} [opts] <container> <cmd>  Execute command in container
    ${CYAN}vshell${NC}                       Open interactive shell in VM (debug)
    ${CYAN}cp${NC} <src> <dest>              Copy files to/from container

  ${BOLD}Registry:${NC}
    ${CYAN}login${NC} [options]              Log in to a registry
    ${CYAN}logout${NC} [registry]            Log out from a registry
    ${CYAN}push${NC} <image>                 Push image to registry
    ${CYAN}search${NC} <term>                Search registries for images

  ${BOLD}System:${NC}
    ${CYAN}info${NC}                         Display system info
    ${CYAN}version${NC}                      Show ${RUNTIME_UPPER} version
    ${CYAN}system df${NC}                    Show disk usage of images/containers/volumes
    ${CYAN}system prune${NC}                 Remove unused data
    ${CYAN}system prune -a${NC}              Remove all unused images

${BOLD}EXTENDED COMMANDS (${VCONTAINER_RUNTIME_NAME}-specific):${NC}
    ${CYAN}vimport${NC} <path> [name:tag]    Import from OCI dir, tarball, or directory (auto-detect)
                                 Multi-arch OCI Image Index supported (auto-selects platform)
    ${CYAN}vrun${NC} [opts] <image> [cmd]    Run command, clearing entrypoint (see RUN vs VRUN below)
    ${CYAN}vstorage${NC}                     List all storage directories (alias: vstorage list)
    ${CYAN}vstorage list${NC}                List all storage directories with details
    ${CYAN}vstorage path [arch]${NC}         Show path to storage directory
    ${CYAN}vstorage df${NC}                  Show detailed disk usage breakdown
    ${CYAN}vstorage clean [arch|--all]${NC}  Clean storage directories (stops memres first)
    ${CYAN}clean${NC}                        ${YELLOW}[DEPRECATED]${NC} Use 'vstorage clean' instead

${BOLD}MEMORY RESIDENT MODE (vmemres):${NC}
    By default, vmemres auto-starts on the first command and stops after idle timeout.
    This provides fast command execution without manual daemon management.

    ${CYAN}vmemres start${NC}                Start memory resident VM in background
    ${CYAN}vmemres stop${NC}                 Stop memory resident VM
    ${CYAN}vmemres restart${NC} [--clean]    Restart VM (optionally clean state first)
    ${CYAN}vmemres status${NC}               Show memory resident VM status
    ${CYAN}vmemres list${NC}                 List all running memres instances
    (Note: 'memres' also works as an alias for 'vmemres')

    Auto-start and idle timeout:
      - Daemon auto-starts when you run any command (configurable via vconfig auto-daemon)
      - Daemon auto-stops after 30 minutes of inactivity (configurable via vconfig idle-timeout)
      - Use --no-daemon to run commands in ephemeral mode (no daemon)

    ${BOLD}Dynamic Port Forwarding:${NC}
      When running detached containers with -p, port forwards are added dynamically:
        ${PROG_NAME} run -d -p 8080:80 nginx        # Adds 8080->80 forward
        ${PROG_NAME} run -d -p 3000:3000 myapp      # Adds 3000->3000 forward
        ${PROG_NAME} ps                              # Shows containers AND port forwards
        ${PROG_NAME} stop nginx                      # Removes 8080->80 forward

      Port format: -p <host_port>:<container_port>[/protocol]
        - protocol: tcp (default) or udp
        - Multiple -p options can be specified

    ${YELLOW}NOTE:${NC} Docker bridge networking (docker0) is used by default.
    Each container gets its own IP on 172.17.0.0/16. Port forwarding works via:
      Host:8080 -> QEMU -> VM:8080 -> Docker iptables -> Container:80
    Use --network=host for legacy behavior where containers share VM's network.
    Use --network=none to disable networking entirely.

${BOLD}RUN vs VRUN:${NC}
    ${CYAN}run${NC}   - Full ${RUNTIME_UPPER} passthrough. Entrypoint is honored.
            Command args are passed TO the entrypoint.
            Example: run alpine /bin/sh    -> entrypoint receives '/bin/sh' as arg
    ${CYAN}vrun${NC}  - Convenience wrapper. Clears entrypoint when command given.
            Command args become the container's command directly.
            Example: vrun alpine /bin/sh   -> runs /bin/sh as PID 1

    Use 'run' when you need --entrypoint, -e, --rm, or other ${VCONTAINER_RUNTIME_CMD} options.
    Use 'vrun' for simple "run this command in image" cases.

${BOLD}CONFIGURATION (vconfig):${NC}
    ${CYAN}vconfig${NC}                      Show all configuration values
    ${CYAN}vconfig${NC} <key>                Get configuration value
    ${CYAN}vconfig${NC} <key> <value>        Set configuration value
    ${CYAN}vconfig${NC} <key> --reset        Reset to default value

    Supported keys: arch, timeout, state-dir, verbose, idle-timeout, auto-daemon, registry
    Config file: \$CONFIG_DIR/config (default: ~/.config/${VCONTAINER_RUNTIME_NAME}/config)

    idle-timeout: Daemon idle timeout in seconds [default: 1800]
    auto-daemon:  Auto-start daemon on first command [default: true]
    registry:     Default registry for unqualified images (e.g., 10.0.2.2:5000/yocto)

${BOLD}GLOBAL OPTIONS:${NC}
    --arch, -a <arch>     Target architecture: x86_64 or aarch64 [default: ${DEFAULT_ARCH}]
    --config-dir <path>   Configuration directory [default: ~/.config/${VCONTAINER_RUNTIME_NAME}]
    --instance, -I <name> Use named instance (shortcut for --state-dir ~/.$VCONTAINER_RUNTIME_NAME/<name>)
    --blob-dir <path>     Path to kernel/initramfs blobs (override default)
    --stateless           Start with fresh ${RUNTIME_UPPER} state (no persistence)
    --state-dir <path>    Override state directory [default: ~/.$VCONTAINER_RUNTIME_NAME/<arch>]
    --storage <file>      Export ${VCONTAINER_RUNTIME_CMD} storage after command (tar file)
    --input-storage <tar> Load ${RUNTIME_UPPER} state from tar before command
    --no-kvm              Disable KVM acceleration (use TCG emulation)
    --no-daemon           Run in ephemeral mode (don't auto-start/use daemon)
    --registry <url>      Default registry for unqualified images (e.g., 10.0.2.2:5000/yocto)
    --no-registry         Disable baked-in default registry (use images as-is)
    --insecure-registry <host:port>  Mark registry as insecure (HTTP). Can repeat.
    --verbose, -v         Enable verbose output
    --help, -h            Show this help

${BOLD}${RUNTIME_UPPER} RUN/VRUN OPTIONS:${NC}
    All ${VCONTAINER_RUNTIME_CMD} run options are passed through (e.g., -it, -e, -p, --rm, etc.)
    Interactive mode (-it) automatically handles daemon stop/restart
    -v <host>:<container>[:mode]  Mount host path in container (requires vmemres)
                                   mode: ro (read-only) or rw (read-write, default)

${BOLD}EXAMPLES:${NC}
    # List images (uses persistent state by default)
    ${PROG_NAME} images

    # Import rootfs tarball (matches '${VCONTAINER_RUNTIME_CMD} import' exactly)
    ${PROG_NAME} import rootfs.tar myapp:latest

    # Import OCI directory (extended command, auto-detects format)
    ${PROG_NAME} vimport ./container-oci/ myapp:latest
    ${PROG_NAME} images        # Image persists!

    # Save image to tar archive
    ${PROG_NAME} save -o myapp.tar myapp:latest

    # Load a ${RUNTIME_UPPER} image archive (from '${VCONTAINER_RUNTIME_CMD} save')
    ${PROG_NAME} load -i myapp.tar

    # Start fresh (ignore existing state)
    ${PROG_NAME} --stateless images

    # Export storage for deployment to target
    ${PROG_NAME} --storage /tmp/${VCONTAINER_RUNTIME_CMD}-storage.tar vimport ./container-oci/ myapp:latest

    # Run a command in a container (${VCONTAINER_RUNTIME_CMD}-compatible syntax)
    ${PROG_NAME} run alpine /bin/echo hello
    ${PROG_NAME} run --rm alpine uname -m      # Check container architecture

    # Interactive shell (${VCONTAINER_RUNTIME_CMD}-compatible syntax)
    ${PROG_NAME} run -it alpine /bin/sh

    # With environment variables and other ${VCONTAINER_RUNTIME_CMD} options
    ${PROG_NAME} run --rm -e FOO=bar myapp:latest
    ${PROG_NAME} run -it -p 8080:80 nginx:latest

    # Pull an image from a registry
    ${PROG_NAME} pull alpine:latest

    # Pull from local registry (configure once, use everywhere)
    ${PROG_NAME} vconfig registry 10.0.2.2:5000/yocto    # Set default registry
    ${PROG_NAME} pull container-base                     # Pulls from 10.0.2.2:5000/yocto/container-base

    # Or use --registry for one-off pulls
    ${PROG_NAME} --registry 10.0.2.2:5000/yocto pull container-base

    # vrun: convenience wrapper (clears entrypoint when command given)
    ${PROG_NAME} vrun myapp:latest /bin/ls -la  # Runs /bin/ls directly, not via entrypoint

    # Volume mounts (requires memres to be running)
    ${PROG_NAME} memres start
    ${PROG_NAME} vrun -v /tmp/data:/data alpine cat /data/file.txt
    ${PROG_NAME} vrun -v /home/user/src:/src:ro alpine ls /src
    ${PROG_NAME} run -v ./local:/app --rm myapp:latest /app/run.sh

    # Port forwarding (web server)
    ${PROG_NAME} memres start -p 8080:80           # Forward host:8080 to guest:80
    ${PROG_NAME} run -d --rm nginx:alpine          # Run nginx (--network=host is default)
    curl http://localhost:8080                     # Access nginx from host

    # Port forwarding (SSH into a container)
    ${PROG_NAME} memres start -p 2222:22           # Forward host:2222 to guest:22
    ${PROG_NAME} run -d my-ssh-image               # Container with SSH server
    ssh -p 2222 localhost                          # SSH from host into container

    # Multiple instances with different ports
    ${PROG_NAME} memres list                       # Show running instances
    ${PROG_NAME} -I web memres start -p 8080:80    # Start named instance
    ${PROG_NAME} -I web images                     # Use named instance
    ${PROG_NAME} -I backend run -d my-api:latest

${BOLD}NOTES:${NC}
    - Architecture detection (in priority order):
        1. --arch / -a flag
        2. Executable name (${VCONTAINER_RUNTIME_NAME}-aarch64 or ${VCONTAINER_RUNTIME_NAME}-x86_64)
        3. ${VCONTAINER_RUNTIME_PREFIX}_ARCH environment variable
        4. Config file: ~/.config/${VCONTAINER_RUNTIME_NAME}/arch
        5. Host architecture (uname -m)
    - Current architecture: ${DEFAULT_ARCH}
    - State persists in ~/.$VCONTAINER_RUNTIME_NAME/<arch>/
    - Use --stateless for fresh ${RUNTIME_UPPER} state each run
    - Use --storage to export ${RUNTIME_UPPER} storage to tar file
    - run vs vrun:
        run  = exact ${VCONTAINER_RUNTIME_CMD} run syntax (entrypoint honored)
        vrun = clears entrypoint when command given (runs command directly)

${BOLD}ENVIRONMENT:${NC}
    ${VCONTAINER_RUNTIME_PREFIX}_BLOB_DIR   Path to kernel/initramfs blobs
    ${VCONTAINER_RUNTIME_PREFIX}_STATE_DIR  Base directory for state [default: ~/.$VCONTAINER_RUNTIME_NAME]
    ${VCONTAINER_RUNTIME_PREFIX}_STATELESS  Run stateless by default (true/false)
    ${VCONTAINER_RUNTIME_PREFIX}_VERBOSE    Enable verbose output (true/false)

EOF
}

# Build runner args
build_runner_args() {
    local args=()

    # Specify runtime (docker for vdkr, podman for vpdmn)
    args+=("--runtime" "$VCONTAINER_RUNTIME_CMD")
    args+=("--arch" "$TARGET_ARCH")

    [ -n "$BLOB_DIR" ] && args+=("--blob-dir" "$BLOB_DIR")
    [ "$VERBOSE" = "true" ] && args+=("--verbose")
    [ "$NETWORK" = "true" ] && args+=("--network")
    [ "$INTERACTIVE" = "true" ] && args+=("--interactive")
    [ -n "$STORAGE_OUTPUT" ] && args+=("--output-type" "storage" "--output" "$STORAGE_OUTPUT")
    [ -n "$STATE_DIR" ] && args+=("--state-dir" "$STATE_DIR")
    [ -n "$INPUT_STORAGE" ] && args+=("--input-storage" "$INPUT_STORAGE")
    [ "$DISABLE_KVM" = "true" ] && args+=("--no-kvm")

    # Add idle timeout from config
    local idle_timeout=$(config_get "idle-timeout" "1800")
    args+=("--idle-timeout" "$idle_timeout")

    # Add port forwards (each -p adds a --port-forward)
    for pf in "${PORT_FORWARDS[@]}"; do
        args+=("--port-forward" "$pf")
    done

    # Add registry configuration
    [ -n "$REGISTRY" ] && args+=("--registry" "$REGISTRY")
    for reg in "${INSECURE_REGISTRIES[@]}"; do
        args+=("--insecure-registry" "$reg")
    done

    echo "${args[@]}"
}

# Parse global options first
TARGET_ARCH="$DEFAULT_ARCH"
STORAGE_OUTPUT=""
STATE_DIR=""
INPUT_STORAGE=""
NETWORK="true"
INTERACTIVE="false"
PORT_FORWARDS=()
DISABLE_KVM="false"
NO_DAEMON="false"
REGISTRY=""
INSECURE_REGISTRIES=()
COMMAND=""
COMMAND_ARGS=()

while [ $# -gt 0 ]; do
    case $1 in
        --arch|-a)
            # Only parse as global option before command is set
            if [ -z "$COMMAND" ]; then
                TARGET_ARCH="$2"
                shift 2
            else
                COMMAND_ARGS+=("$1")
                shift
            fi
            ;;
        --blob-dir)
            BLOB_DIR="$2"
            shift 2
            ;;
        --storage)
            STORAGE_OUTPUT="$2"
            shift 2
            ;;
        --state-dir)
            STATE_DIR="$2"
            shift 2
            ;;
        --instance|-I)
            # Shortcut: -I web expands to --state-dir ~/.$VCONTAINER_RUNTIME_NAME/web
            STATE_DIR="$DEFAULT_STATE_DIR/$2"
            shift 2
            ;;
        --config-dir)
            # Already pre-parsed, just consume it
            shift 2
            ;;
        --config-dir=*)
            # Already pre-parsed, just consume it
            shift
            ;;
        --input-storage)
            INPUT_STORAGE="$2"
            shift 2
            ;;
        --stateless)
            STATELESS="true"
            shift
            ;;
        --no-network)
            NETWORK="false"
            shift
            ;;
        --no-kvm)
            DISABLE_KVM="true"
            shift
            ;;
        --no-daemon)
            NO_DAEMON="true"
            shift
            ;;
        --registry)
            REGISTRY="$2"
            shift 2
            ;;
        --no-registry)
            # Explicitly disable baked-in registry (passes docker_registry=none to init)
            REGISTRY="none"
            shift
            ;;
        --insecure-registry)
            INSECURE_REGISTRIES+=("$2")
            shift 2
            ;;
        -it|--interactive)
            INTERACTIVE="true"
            shift
            ;;
        -i)
            # -i alone means interactive, but only before we have a command
            # After a command, -i might be an argument (e.g., load -i file)
            if [ -z "$COMMAND" ]; then
                INTERACTIVE="true"
            else
                COMMAND_ARGS+=("$1")
            fi
            shift
            ;;
        -t)
            # -t alone means interactive (allocate TTY) before command
            # After command, -t might be an argument
            if [ -z "$COMMAND" ]; then
                INTERACTIVE="true"
            else
                COMMAND_ARGS+=("$1")
            fi
            shift
            ;;
        --verbose)
            VERBOSE="true"
            shift
            ;;
        -v)
            # -v can mean verbose (before command) or volume (after command like run/vrun)
            if [ -z "$COMMAND" ]; then
                VERBOSE="true"
            else
                # After command, -v is likely a volume flag - pass to subcommand
                COMMAND_ARGS+=("$1")
            fi
            shift
            ;;
        --help|-h)
            show_usage
            exit 0
            ;;
        --version)
            echo "$VCONTAINER_RUNTIME_NAME version $VCONTAINER_VERSION"
            exit 0
            ;;
        -*)
            # Unknown option - might be for subcommand
            COMMAND_ARGS+=("$1")
            shift
            ;;
        *)
            if [ -z "$COMMAND" ]; then
                COMMAND="$1"
            else
                COMMAND_ARGS+=("$1")
            fi
            shift
            ;;
    esac
done

if [ -z "$COMMAND" ]; then
    show_usage
    exit 0
fi

# Set up state directory (default to persistent unless --stateless)
if [ "$STATELESS" != "true" ] && [ -z "$STATE_DIR" ] && [ -z "$INPUT_STORAGE" ]; then
    STATE_DIR="$DEFAULT_STATE_DIR/$TARGET_ARCH"
fi

# Read registry from config if not set via CLI
if [ -z "$REGISTRY" ]; then
    REGISTRY=$(config_get "registry" "")
fi

# Check runner exists
if [ ! -x "$RUNNER" ]; then
    echo -e "${RED}[$VCONTAINER_RUNTIME_NAME]${NC} Runner script not found: $RUNNER" >&2
    exit 1
fi

# Helper function to check if daemon is running
daemon_is_running() {
    # Use STATE_DIR if set, otherwise use default
    local state_dir="${STATE_DIR:-$DEFAULT_STATE_DIR/$TARGET_ARCH}"
    local pid_file="$state_dir/daemon.pid"
    if [ -f "$pid_file" ]; then
        local pid=$(cat "$pid_file")
        if [ -d "/proc/$pid" ]; then
            return 0
        fi
    fi
    return 1
}

# ============================================================================
# QMP (QEMU Machine Protocol) Helpers for Dynamic Port Forwarding
# ============================================================================
# These functions communicate with QEMU's QMP socket to add/remove port
# forwards at runtime without restarting the daemon.

# Get the QMP socket path
get_qmp_socket() {
    local state_dir="${STATE_DIR:-$DEFAULT_STATE_DIR/$TARGET_ARCH}"
    echo "$state_dir/qmp.sock"
}

# Send a QMP command and get the response
# Usage: qmp_send "command-line"
qmp_send() {
    local cmd="$1"
    local qmp_socket=$(get_qmp_socket)

    if [ ! -S "$qmp_socket" ]; then
        echo "QMP socket not found: $qmp_socket" >&2
        return 1
    fi

    # QMP requires a capabilities negotiation first, then human-monitor-command
    # We use socat to send the command
    {
        echo '{"execute":"qmp_capabilities"}'
        sleep 0.1
        echo "{\"execute\":\"human-monitor-command\",\"arguments\":{\"command-line\":\"$cmd\"}}"
    } | socat - "unix-connect:$qmp_socket" 2>/dev/null
}

# Add a port forward to the running daemon
# Usage: qmp_add_hostfwd <host_port> <guest_port> [protocol]
# With bridge networking: QEMU forwards host:port -> VM:port, Docker handles VM:port -> container:port
qmp_add_hostfwd() {
    local host_port="$1"
    local guest_port="$2"
    local protocol="${3:-tcp}"

    [ "$VERBOSE" = "true" ] && echo -e "${CYAN}[$VCONTAINER_RUNTIME_NAME]${NC} Adding port forward: ${host_port} -> ${guest_port}/${protocol}" >&2

    # QEMU forwards to host_port on VM; Docker -p handles the container port mapping
    local result=$(qmp_send "hostfwd_add net0 ${protocol}::${host_port}-:${host_port}")
    if echo "$result" | grep -q '"error"'; then
        echo -e "${RED}[$VCONTAINER_RUNTIME_NAME]${NC} Failed to add port forward: $result" >&2
        return 1
    fi
    return 0
}

# Remove a port forward from the running daemon
# Usage: qmp_remove_hostfwd <host_port> [protocol]
qmp_remove_hostfwd() {
    local host_port="$1"
    local protocol="${2:-tcp}"

    [ "$VERBOSE" = "true" ] && echo -e "${CYAN}[$VCONTAINER_RUNTIME_NAME]${NC} Removing port forward: ${host_port}/${protocol}" >&2

    local result=$(qmp_send "hostfwd_remove net0 ${protocol}::${host_port}")
    if echo "$result" | grep -q '"error"'; then
        echo -e "${RED}[$VCONTAINER_RUNTIME_NAME]${NC} Failed to remove port forward: $result" >&2
        return 1
    fi
    return 0
}

# ============================================================================
# Port Forward Registry
# ============================================================================
# Track which ports are forwarded for which containers so we can clean up
# when containers are stopped.

PORT_FORWARD_FILE=""

get_port_forward_file() {
    if [ -z "$PORT_FORWARD_FILE" ]; then
        local state_dir="${STATE_DIR:-$DEFAULT_STATE_DIR/$TARGET_ARCH}"
        PORT_FORWARD_FILE="$state_dir/port-forwards.txt"
    fi
    echo "$PORT_FORWARD_FILE"
}

# Register a port forward for a container
# Usage: register_port_forward <container_name> <host_port> <guest_port> [protocol]
register_port_forward() {
    local container_name="$1"
    local host_port="$2"
    local guest_port="$3"
    local protocol="${4:-tcp}"

    local pf_file=$(get_port_forward_file)
    mkdir -p "$(dirname "$pf_file")"
    echo "${container_name}:${host_port}:${guest_port}:${protocol}" >> "$pf_file"
}

# Unregister and remove port forwards for a container
# Usage: unregister_port_forwards <container_name>
unregister_port_forwards() {
    local container_name="$1"
    local pf_file=$(get_port_forward_file)

    if [ ! -f "$pf_file" ]; then
        return 0
    fi

    # Find and remove all port forwards for this container
    local temp_file="${pf_file}.tmp"
    while IFS=: read -r name host_port guest_port protocol; do
        if [ "$name" = "$container_name" ]; then
            qmp_remove_hostfwd "$host_port" "$protocol"
        else
            echo "${name}:${host_port}:${guest_port}:${protocol}"
        fi
    done < "$pf_file" > "$temp_file"
    mv "$temp_file" "$pf_file"
}

# List all registered port forwards
# Usage: list_port_forwards [container_name]
list_port_forwards() {
    local filter_name="$1"
    local pf_file=$(get_port_forward_file)

    if [ ! -f "$pf_file" ]; then
        return 0
    fi

    while IFS=: read -r name host_port guest_port protocol; do
        if [ -z "$filter_name" ] || [ "$name" = "$filter_name" ]; then
            echo "0.0.0.0:${host_port}->${guest_port}/${protocol}"
        fi
    done < "$pf_file"
}

# Helper function to run command via daemon or regular mode
run_runtime_command() {
    local runtime_cmd="$1"
    local runner_args=$(build_runner_args)

    # Check for --no-daemon flag - use ephemeral mode
    if [ "$NO_DAEMON" = "true" ]; then
        [ "$VERBOSE" = "true" ] && echo -e "${CYAN}[$VCONTAINER_RUNTIME_NAME]${NC} Using ephemeral mode (--no-daemon)" >&2
        "$RUNNER" $runner_args -- "$runtime_cmd"
        return $?
    fi

    if daemon_is_running; then
        # Use daemon mode - faster
        [ "$VERBOSE" = "true" ] && echo -e "${CYAN}[$VCONTAINER_RUNTIME_NAME]${NC} Using daemon mode" >&2
        "$RUNNER" $runner_args --daemon-send "$runtime_cmd"
    else
        # Check if auto-daemon is enabled
        local auto_daemon=$(config_get "auto-daemon" "true")
        if [ "$auto_daemon" = "true" ]; then
            # Auto-start daemon (idle-timeout is included in runner_args)
            echo -e "${CYAN}[$VCONTAINER_RUNTIME_NAME]${NC} Starting daemon..." >&2
            "$RUNNER" $runner_args --daemon-start

            if daemon_is_running; then
                # Fresh daemon has no port forwards - clear stale registry
                local pf_file=$(get_port_forward_file)
                if [ -f "$pf_file" ]; then
                    [ "$VERBOSE" = "true" ] && echo -e "${CYAN}[$VCONTAINER_RUNTIME_NAME]${NC} Clearing stale port forward registry" >&2
                    rm -f "$pf_file"
                fi
                [ "$VERBOSE" = "true" ] && echo -e "${CYAN}[$VCONTAINER_RUNTIME_NAME]${NC} Using daemon mode" >&2
                "$RUNNER" $runner_args --daemon-send "$runtime_cmd"
            else
                echo -e "${RED}[$VCONTAINER_RUNTIME_NAME]${NC} Failed to start daemon, using ephemeral mode" >&2
                "$RUNNER" $runner_args -- "$runtime_cmd"
            fi
        else
            # Auto-daemon disabled - use ephemeral mode
            "$RUNNER" $runner_args -- "$runtime_cmd"
        fi
    fi
}

# Helper function to run command with input
# Uses daemon mode with virtio-9p if daemon is running, otherwise regular mode
run_runtime_command_with_input() {
    local input_path="$1"
    local input_type="$2"
    local runtime_cmd="$3"
    local runner_args=$(build_runner_args)

    # Check for --no-daemon flag - use ephemeral mode
    if [ "$NO_DAEMON" = "true" ]; then
        [ "$VERBOSE" = "true" ] && echo -e "${CYAN}[$VCONTAINER_RUNTIME_NAME]${NC} Using ephemeral mode (--no-daemon)" >&2
        "$RUNNER" $runner_args --input "$input_path" --input-type "$input_type" -- "$runtime_cmd"
        return $?
    fi

    if daemon_is_running; then
        # Use daemon mode with virtio-9p shared directory
        [ "$VERBOSE" = "true" ] && echo -e "${CYAN}[$VCONTAINER_RUNTIME_NAME]${NC} Using daemon mode for file I/O" >&2
        "$RUNNER" $runner_args --input "$input_path" --input-type "$input_type" --daemon-send-input -- "$runtime_cmd"
    else
        # Check if auto-daemon is enabled
        local auto_daemon=$(config_get "auto-daemon" "true")
        if [ "$auto_daemon" = "true" ]; then
            # Auto-start daemon (idle-timeout is included in runner_args)
            echo -e "${CYAN}[$VCONTAINER_RUNTIME_NAME]${NC} Starting daemon..." >&2
            "$RUNNER" $runner_args --daemon-start

            if daemon_is_running; then
                # Fresh daemon has no port forwards - clear stale registry
                local pf_file=$(get_port_forward_file)
                if [ -f "$pf_file" ]; then
                    [ "$VERBOSE" = "true" ] && echo -e "${CYAN}[$VCONTAINER_RUNTIME_NAME]${NC} Clearing stale port forward registry" >&2
                    rm -f "$pf_file"
                fi
                [ "$VERBOSE" = "true" ] && echo -e "${CYAN}[$VCONTAINER_RUNTIME_NAME]${NC} Using daemon mode for file I/O" >&2
                "$RUNNER" $runner_args --input "$input_path" --input-type "$input_type" --daemon-send-input -- "$runtime_cmd"
            else
                echo -e "${RED}[$VCONTAINER_RUNTIME_NAME]${NC} Failed to start daemon, using ephemeral mode" >&2
                "$RUNNER" $runner_args --input "$input_path" --input-type "$input_type" -- "$runtime_cmd"
            fi
        else
            # Auto-daemon disabled - use ephemeral mode
            "$RUNNER" $runner_args --input "$input_path" --input-type "$input_type" -- "$runtime_cmd"
        fi
    fi
}

# ============================================================================
# Volume Mount Support
# ============================================================================
# Volumes are copied to the share directory before running the container.
# Format: -v /host/path:/container/path[:ro|:rw]
#
# Implementation:
#   - Copy host path to $SHARE_DIR/volumes/<hash>/
#   - Transform -v to use /mnt/share/volumes/<hash>:/container/path
#   - After container exits, sync back for :rw mounts (default)
#
# Limitations:
#   - Requires daemon mode (memres) for volume mounts
#   - Changes in container are synced back after container exits (not real-time)
#   - Large volumes may be slow to copy
# ============================================================================

# Array to track volume mounts for cleanup/sync
declare -a VOLUME_MOUNTS=()
declare -a VOLUME_MODES=()

# Generate a short hash for volume directory naming
volume_hash() {
    echo "$1" | md5sum | cut -c1-8
}

# Global to receive result from prepare_volume (avoids subshell issue)
PREPARE_VOLUME_RESULT=""

# Prepare a volume mount: copy host path to share directory
# Sets PREPARE_VOLUME_RESULT to the guest path (avoids subshell issue with arrays)
prepare_volume() {
    local host_path="$1"
    local container_path="$2"
    local mode="$3"  # ro or rw (default: rw)

    [ -z "$mode" ] && mode="rw"
    PREPARE_VOLUME_RESULT=""

    # Validate host path exists
    if [ ! -e "$host_path" ]; then
        echo -e "${RED}[$VCONTAINER_RUNTIME_NAME]${NC} Volume source not found: $host_path" >&2
        return 1
    fi

    # Get share directory
    local share_dir="${STATE_DIR:-$DEFAULT_STATE_DIR/$TARGET_ARCH}/share"
    local volumes_dir="$share_dir/volumes"

    # Create volumes directory
    mkdir -p "$volumes_dir"

    # Generate unique directory name based on host path
    local hash=$(volume_hash "$host_path")
    local vol_dir="$volumes_dir/$hash"

    # Clean and copy
    rm -rf "$vol_dir"
    mkdir -p "$vol_dir"

    if [ -d "$host_path" ]; then
        # Directory: copy contents
        cp -rL "$host_path"/* "$vol_dir/" 2>/dev/null || true
        [ "$VERBOSE" = "true" ] && echo -e "${CYAN}[$VCONTAINER_RUNTIME_NAME]${NC} Volume: copied directory $host_path -> /mnt/share/volumes/$hash" >&2
    else
        # File: copy file
        cp -L "$host_path" "$vol_dir/"
        [ "$VERBOSE" = "true" ] && echo -e "${CYAN}[$VCONTAINER_RUNTIME_NAME]${NC} Volume: copied file $host_path -> /mnt/share/volumes/$hash" >&2
    fi

    # Sync to ensure data is visible to guest
    sync

    # Track for later sync-back (in parent shell, not subshell)
    VOLUME_MOUNTS+=("$host_path:$vol_dir:$container_path")
    VOLUME_MODES+=("$mode")

    # Set result in global variable (caller reads this, not $(prepare_volume))
    PREPARE_VOLUME_RESULT="/mnt/share/volumes/$hash"
}

# Sync volumes back from guest to host (for :rw mounts)
sync_volumes_back() {
    local share_dir="${STATE_DIR:-$DEFAULT_STATE_DIR/$TARGET_ARCH}/share"
    local volumes_dir="$share_dir/volumes"

    # Wait for 9p filesystem to sync writes from guest to host
    sleep 1
    sync

    for i in "${!VOLUME_MOUNTS[@]}"; do
        local mount="${VOLUME_MOUNTS[$i]}"
        local mode="${VOLUME_MODES[$i]}"

        if [ "$mode" = "rw" ]; then
            # Parse mount string: host_path:vol_dir:container_path
            local host_path=$(echo "$mount" | cut -d: -f1)
            local vol_dir=$(echo "$mount" | cut -d: -f2)

            if [ -d "$vol_dir" ] && [ -d "$host_path" ]; then
                [ "$VERBOSE" = "true" ] && echo -e "${CYAN}[$VCONTAINER_RUNTIME_NAME]${NC} Syncing volume back: $vol_dir -> $host_path" >&2
                # Use rsync if available, otherwise cp
                if command -v rsync >/dev/null 2>&1; then
                    rsync -a --delete "$vol_dir/" "$host_path/"
                else
                    rm -rf "$host_path"/*
                    cp -rL "$vol_dir"/* "$host_path/" 2>/dev/null || true
                fi
            elif [ -f "$host_path" ]; then
                # Single file mount
                local filename=$(basename "$host_path")
                if [ -f "$vol_dir/$filename" ]; then
                    cp -L "$vol_dir/$filename" "$host_path"
                fi
            fi
        fi
    done
}

# Clean up volume directories
cleanup_volumes() {
    local share_dir="${STATE_DIR:-$DEFAULT_STATE_DIR/$TARGET_ARCH}/share"
    local volumes_dir="$share_dir/volumes"

    if [ -d "$volumes_dir" ]; then
        rm -rf "$volumes_dir"
    fi

    # Clear tracking arrays
    VOLUME_MOUNTS=()
    VOLUME_MODES=()
}

# Global variable to hold transformed volume arguments
TRANSFORMED_VOLUME_ARGS=()

# Parse volume mounts from arguments and transform them
# Input: array elements passed as arguments
# Output: sets TRANSFORMED_VOLUME_ARGS with transformed arguments
# Side effect: populates VOLUME_MOUNTS array
parse_and_prepare_volumes() {
    TRANSFORMED_VOLUME_ARGS=()
    local args=("$@")
    local i=0

    while [ $i -lt ${#args[@]} ]; do
        local arg="${args[$i]}"

        case "$arg" in
            -v|--volume)
                # Next arg is the volume spec
                i=$((i + 1))
                if [ $i -ge ${#args[@]} ]; then
                    echo -e "${RED}[$VCONTAINER_RUNTIME_NAME]${NC} -v requires an argument" >&2
                    return 1
                fi
                local vol_spec="${args[$i]}"

                # Parse volume spec: host:container[:mode]
                local host_path=$(echo "$vol_spec" | cut -d: -f1)
                local container_path=$(echo "$vol_spec" | cut -d: -f2)
                local mode=$(echo "$vol_spec" | cut -d: -f3)

                # Make host path absolute
                if [[ "$host_path" != /* ]]; then
                    host_path="$(pwd)/$host_path"
                fi

                # Prepare volume (sets PREPARE_VOLUME_RESULT global)
                prepare_volume "$host_path" "$container_path" "$mode" || return 1
                local guest_path="$PREPARE_VOLUME_RESULT"

                # Add transformed volume option
                if [ -d "$host_path" ]; then
                    TRANSFORMED_VOLUME_ARGS+=("-v" "${guest_path}:${container_path}${mode:+:$mode}")
                else
                    # For single file, include filename
                    local filename=$(basename "$host_path")
                    TRANSFORMED_VOLUME_ARGS+=("-v" "${guest_path}/${filename}:${container_path}${mode:+:$mode}")
                fi
                ;;
            *)
                TRANSFORMED_VOLUME_ARGS+=("$arg")
                ;;
        esac
        i=$((i + 1))
    done
}

# Handle commands
case "$COMMAND" in
    image)
        # Handle "docker image *" compound commands
        # docker image ls → docker images
        # docker image rm → docker rmi
        # docker image pull → docker pull
        # etc.
        if [ ${#COMMAND_ARGS[@]} -lt 1 ]; then
            echo -e "${RED}[$VCONTAINER_RUNTIME_NAME]${NC} image requires a subcommand (ls, rm, pull, inspect, tag, push, prune)" >&2
            exit 1
        fi
        SUBCMD="${COMMAND_ARGS[0]}"
        SUBCMD_ARGS=("${COMMAND_ARGS[@]:1}")
        case "$SUBCMD" in
            ls|list)
                run_runtime_command "$VCONTAINER_RUNTIME_CMD images ${SUBCMD_ARGS[*]}"
                ;;
            rm|remove)
                run_runtime_command "$VCONTAINER_RUNTIME_CMD rmi ${SUBCMD_ARGS[*]}"
                ;;
            pull)
                # Reuse pull logic - set COMMAND_ARGS and fall through
                COMMAND_ARGS=("${SUBCMD_ARGS[@]}")
                if [ ${#COMMAND_ARGS[@]} -lt 1 ]; then
                    echo -e "${RED}[$VCONTAINER_RUNTIME_NAME]${NC} image pull requires <image>" >&2
                    exit 1
                fi
                IMAGE_NAME="${COMMAND_ARGS[0]}"
                if daemon_is_running; then
                    run_runtime_command "$VCONTAINER_RUNTIME_CMD pull $IMAGE_NAME && $VCONTAINER_RUNTIME_CMD images"
                else
                    NETWORK="true"
                    RUNNER_ARGS=$(build_runner_args)
                    "$RUNNER" $RUNNER_ARGS -- "$VCONTAINER_RUNTIME_CMD pull $IMAGE_NAME && $VCONTAINER_RUNTIME_CMD images"
                fi
                ;;
            inspect)
                run_runtime_command "$VCONTAINER_RUNTIME_CMD inspect ${SUBCMD_ARGS[*]}"
                ;;
            tag)
                run_runtime_command "$VCONTAINER_RUNTIME_CMD tag ${SUBCMD_ARGS[*]}"
                ;;
            push)
                NETWORK="true"
                run_runtime_command "$VCONTAINER_RUNTIME_CMD push ${SUBCMD_ARGS[*]}"
                ;;
            prune)
                run_runtime_command "$VCONTAINER_RUNTIME_CMD image prune ${SUBCMD_ARGS[*]}"
                ;;
            history)
                run_runtime_command "$VCONTAINER_RUNTIME_CMD history ${SUBCMD_ARGS[*]}"
                ;;
            *)
                echo -e "${RED}[$VCONTAINER_RUNTIME_NAME]${NC} Unknown image subcommand: $SUBCMD" >&2
                echo -e "${YELLOW}[$VCONTAINER_RUNTIME_NAME]${NC} Valid subcommands: ls, rm, pull, inspect, tag, push, prune, history" >&2
                exit 1
                ;;
        esac
        ;;

    images)
        # runtime images
        run_runtime_command "$VCONTAINER_RUNTIME_CMD images ${COMMAND_ARGS[*]}"
        ;;

    pull)
        # runtime pull <image>
        # Daemon mode already has networking enabled, so this works via daemon
        if [ ${#COMMAND_ARGS[@]} -lt 1 ]; then
            echo -e "${RED}[$VCONTAINER_RUNTIME_NAME]${NC} pull requires <image>" >&2
            exit 1
        fi

        IMAGE_NAME="${COMMAND_ARGS[0]}"

        if daemon_is_running; then
            # Use daemon mode (already has networking)
            run_runtime_command "$VCONTAINER_RUNTIME_CMD pull $IMAGE_NAME && $VCONTAINER_RUNTIME_CMD images"
        else
            # Regular mode - need to enable networking
            NETWORK="true"
            RUNNER_ARGS=$(build_runner_args)
            "$RUNNER" $RUNNER_ARGS -- "$VCONTAINER_RUNTIME_CMD pull $IMAGE_NAME && $VCONTAINER_RUNTIME_CMD images"
        fi
        ;;

    load)
        # runtime load -i <file>
        # Parse -i argument
        INPUT_FILE=""
        LOAD_ARGS=()
        i=0
        while [ $i -lt ${#COMMAND_ARGS[@]} ]; do
            arg="${COMMAND_ARGS[$i]}"
            case "$arg" in
                -i|--input)
                    i=$((i + 1))
                    INPUT_FILE="${COMMAND_ARGS[$i]}"
                    ;;
                *)
                    LOAD_ARGS+=("$arg")
                    ;;
            esac
            i=$((i + 1))
        done

        if [ -z "$INPUT_FILE" ]; then
            echo -e "${RED}[$VCONTAINER_RUNTIME_NAME]${NC} load requires -i <file>" >&2
            exit 1
        fi

        if [ ! -f "$INPUT_FILE" ]; then
            echo -e "${RED}[$VCONTAINER_RUNTIME_NAME]${NC} File not found: $INPUT_FILE" >&2
            exit 1
        fi

        run_runtime_command_with_input "$INPUT_FILE" "tar" \
            "$VCONTAINER_RUNTIME_CMD load -i {INPUT}/$(basename "$INPUT_FILE") ${LOAD_ARGS[*]}"
        ;;

    import)
        # runtime import <tarball> [name:tag] - matches Docker/Podman's import exactly
        # Only accepts tarballs (rootfs archives), not OCI directories
        if [ ${#COMMAND_ARGS[@]} -lt 1 ]; then
            echo -e "${RED}[$VCONTAINER_RUNTIME_NAME]${NC} import requires <tarball> [name:tag]" >&2
            echo "For OCI directories, use 'vimport' instead." >&2
            exit 1
        fi

        INPUT_PATH="${COMMAND_ARGS[0]}"
        IMAGE_NAME="${COMMAND_ARGS[1]:-imported:latest}"

        if [ ! -e "$INPUT_PATH" ]; then
            echo -e "${RED}[$VCONTAINER_RUNTIME_NAME]${NC} Not found: $INPUT_PATH" >&2
            exit 1
        fi

        # Only accept files (tarballs), not directories
        if [ -d "$INPUT_PATH" ]; then
            echo -e "${RED}[$VCONTAINER_RUNTIME_NAME]${NC} import only accepts tarballs, not directories" >&2
            echo "For OCI directories, use: $VCONTAINER_RUNTIME_NAME vimport $INPUT_PATH $IMAGE_NAME" >&2
            exit 1
        fi

        run_runtime_command_with_input "$INPUT_PATH" "tar" \
            "$VCONTAINER_RUNTIME_CMD import {INPUT}/$(basename "$INPUT_PATH") $IMAGE_NAME && $VCONTAINER_RUNTIME_CMD images"
        ;;

    vimport)
        # Extended import: handles OCI directories, tarballs, and plain directories
        # Auto-detects format
        if [ ${#COMMAND_ARGS[@]} -lt 1 ]; then
            echo -e "${RED}[$VCONTAINER_RUNTIME_NAME]${NC} vimport requires <path> [name:tag]" >&2
            exit 1
        fi

        INPUT_PATH="${COMMAND_ARGS[0]}"
        IMAGE_NAME="${COMMAND_ARGS[1]:-imported:latest}"

        if [ ! -e "$INPUT_PATH" ]; then
            echo -e "${RED}[$VCONTAINER_RUNTIME_NAME]${NC} Not found: $INPUT_PATH" >&2
            exit 1
        fi

        # Detect input type
        if [ -d "$INPUT_PATH" ]; then
            if [ -f "$INPUT_PATH/index.json" ] || [ -f "$INPUT_PATH/oci-layout" ]; then
                INPUT_TYPE="oci"
                ACTUAL_OCI_PATH="$INPUT_PATH"

                # Check for multi-architecture OCI Image Index
                if is_oci_image_index "$INPUT_PATH"; then
                    local available_platforms=$(get_oci_platforms "$INPUT_PATH")
                    [ "$VERBOSE" = "true" ] && echo -e "${CYAN}[$VCONTAINER_RUNTIME_NAME]${NC} Multi-arch OCI detected. Available: $available_platforms" >&2

                    # Select manifest for target architecture
                    local manifest_digest=$(select_platform_manifest "$INPUT_PATH" "$TARGET_ARCH")
                    if [ -z "$manifest_digest" ]; then
                        echo -e "${RED}[$VCONTAINER_RUNTIME_NAME]${NC} Architecture $TARGET_ARCH not found in multi-arch image" >&2
                        echo -e "${YELLOW}[$VCONTAINER_RUNTIME_NAME]${NC} Available platforms: $available_platforms" >&2
                        echo -e "${YELLOW}[$VCONTAINER_RUNTIME_NAME]${NC} Use --arch <arch> to select a different platform" >&2
                        exit 1
                    fi

                    echo -e "${GREEN}[$VCONTAINER_RUNTIME_NAME]${NC} Selected platform: $OCI_SELECTED_PLATFORM (from multi-arch image)" >&2

                    # Extract single-platform OCI to temp directory
                    TEMP_OCI_DIR=$(mktemp -d)
                    trap "rm -rf '$TEMP_OCI_DIR'" EXIT

                    if ! extract_platform_oci "$INPUT_PATH" "$TEMP_OCI_DIR" "$manifest_digest"; then
                        echo -e "${RED}[$VCONTAINER_RUNTIME_NAME]${NC} Failed to extract platform from multi-arch image" >&2
                        exit 1
                    fi

                    ACTUAL_OCI_PATH="$TEMP_OCI_DIR"
                    [ "$VERBOSE" = "true" ] && echo -e "${CYAN}[$VCONTAINER_RUNTIME_NAME]${NC} Extracted to: $TEMP_OCI_DIR" >&2
                else
                    # Single-arch OCI - check architecture before importing
                    if ! check_oci_arch "$INPUT_PATH" "$TARGET_ARCH"; then
                        exit 1
                    fi
                fi

                # Use skopeo to properly import OCI image with full metadata (entrypoint, cmd, etc.)
                # This preserves the container config unlike raw import
                # For multi-arch, we import from the extracted temp directory
                if [ "$ACTUAL_OCI_PATH" = "$INPUT_PATH" ]; then
                    RUNTIME_CMD="skopeo copy oci:{INPUT} ${VCONTAINER_IMPORT_TARGET}$IMAGE_NAME && $VCONTAINER_RUNTIME_CMD images"
                else
                    # Multi-arch: copy extracted OCI to share dir for import
                    # We need to handle this specially since INPUT_PATH differs from actual OCI
                    INPUT_PATH="$ACTUAL_OCI_PATH"
                    RUNTIME_CMD="skopeo copy oci:{INPUT} ${VCONTAINER_IMPORT_TARGET}$IMAGE_NAME && $VCONTAINER_RUNTIME_CMD images"
                fi
            else
                # Directory but not OCI - check if it looks like a deploy/images dir
                # and provide a helpful hint
                if ls "$INPUT_PATH"/*-oci >/dev/null 2>&1; then
                    echo -e "${RED}[$VCONTAINER_RUNTIME_NAME]${NC} Directory is not an OCI container: $INPUT_PATH" >&2
                    echo -e "${YELLOW}[$VCONTAINER_RUNTIME_NAME]${NC} Found OCI directories inside. Did you mean one of these?" >&2
                    for oci_dir in "$INPUT_PATH"/*-oci; do
                        if [ -d "$oci_dir" ]; then
                            echo "    $(basename "$oci_dir")" >&2
                        fi
                    done
                    echo "" >&2
                    echo "Example: $VCONTAINER_RUNTIME_NAME vimport $INPUT_PATH/$(ls "$INPUT_PATH" | grep -m1 '\-oci$') myimage:latest" >&2
                    exit 1
                fi
                INPUT_TYPE="dir"
                RUNTIME_CMD="$VCONTAINER_RUNTIME_CMD import {INPUT} $IMAGE_NAME && $VCONTAINER_RUNTIME_CMD images"
            fi
        else
            INPUT_TYPE="tar"
            RUNTIME_CMD="$VCONTAINER_RUNTIME_CMD import {INPUT}/$(basename "$INPUT_PATH") $IMAGE_NAME && $VCONTAINER_RUNTIME_CMD images"
        fi

        run_runtime_command_with_input "$INPUT_PATH" "$INPUT_TYPE" "$RUNTIME_CMD"
        ;;

    save)
        # runtime save -o <file> <image>
        OUTPUT_FILE=""
        IMAGE_NAME=""
        i=0
        while [ $i -lt ${#COMMAND_ARGS[@]} ]; do
            arg="${COMMAND_ARGS[$i]}"
            case "$arg" in
                -o|--output)
                    i=$((i + 1))
                    OUTPUT_FILE="${COMMAND_ARGS[$i]}"
                    ;;
                *)
                    IMAGE_NAME="$arg"
                    ;;
            esac
            i=$((i + 1))
        done

        if [ -z "$OUTPUT_FILE" ]; then
            echo -e "${RED}[$VCONTAINER_RUNTIME_NAME]${NC} save requires -o <file>" >&2
            exit 1
        fi

        if [ -z "$IMAGE_NAME" ]; then
            echo -e "${RED}[$VCONTAINER_RUNTIME_NAME]${NC} save requires <image> name" >&2
            exit 1
        fi

        if daemon_is_running; then
            # Use daemon mode with virtio-9p - save to shared dir, then copy to host
            [ "$VERBOSE" = "true" ] && echo -e "${CYAN}[$VCONTAINER_RUNTIME_NAME]${NC} Using daemon mode for save" >&2
            SHARE_DIR="${STATE_DIR:-$DEFAULT_STATE_DIR/$TARGET_ARCH}/share"

            # Clear share dir and run save command
            rm -rf "$SHARE_DIR"/* 2>/dev/null || true
            run_runtime_command "$VCONTAINER_RUNTIME_CMD save -o /mnt/share/output.tar $IMAGE_NAME"

            # Copy from share dir to output file
            if [ -f "$SHARE_DIR/output.tar" ]; then
                cp "$SHARE_DIR/output.tar" "$OUTPUT_FILE"
                rm -f "$SHARE_DIR/output.tar"
                echo -e "${GREEN}[$VCONTAINER_RUNTIME_NAME]${NC} Saved to $OUTPUT_FILE ($(du -h "$OUTPUT_FILE" | cut -f1))"
            else
                echo -e "${RED}[$VCONTAINER_RUNTIME_NAME]${NC} Save failed - output not found in shared directory" >&2
                exit 1
            fi
        else
            # Regular mode - use serial output
            RUNNER_ARGS=$(build_runner_args)
            RUNNER_ARGS=$(echo "$RUNNER_ARGS" | sed 's/--output-type storage//')
            "$RUNNER" $RUNNER_ARGS --output-type tar --output "$OUTPUT_FILE" \
                -- "$VCONTAINER_RUNTIME_CMD save -o /tmp/output.tar $IMAGE_NAME"
        fi
        ;;

    tag|rmi)
        # Commands that work with existing images
        run_runtime_command "$VCONTAINER_RUNTIME_CMD $COMMAND ${COMMAND_ARGS[*]}"
        ;;

    # Container lifecycle commands
    ps)
        # List containers and show port forwards if daemon is running
        run_runtime_command "$VCONTAINER_RUNTIME_CMD ps ${COMMAND_ARGS[*]}"
        PS_EXIT=$?

        # Show host port forwards if daemon is running and we have any
        # Skip if -q/--quiet flag is present (only container IDs requested)
        PS_QUIET=false
        for arg in "${COMMAND_ARGS[@]}"; do
            case "$arg" in
                -q|--quiet) PS_QUIET=true; break ;;
            esac
        done

        if [ "$PS_QUIET" = "false" ] && daemon_is_running; then
            pf_file=$(get_port_forward_file)
            if [ -f "$pf_file" ] && [ -s "$pf_file" ]; then
                echo ""
                echo -e "${CYAN}Host Port Forwards (QEMU):${NC}"
                printf "  %-20s %-15s %-15s %-8s\n" "CONTAINER" "HOST PORT" "GUEST PORT" "PROTO"
                while IFS=: read -r name host_port guest_port protocol; do
                    printf "  %-20s %-15s %-15s %-8s\n" "$name" "0.0.0.0:$host_port" "$guest_port" "${protocol:-tcp}"
                done < "$pf_file"
            fi
        fi
        exit $PS_EXIT
        ;;

    rm)
        # Remove containers and cleanup any registered port forwards
        for arg in "${COMMAND_ARGS[@]}"; do
            # Skip flags like -f, --force, etc.
            case "$arg" in
                -*) continue ;;
            esac
            # This is a container name/id - clean up its port forwards
            if daemon_is_running; then
                unregister_port_forwards "$arg"
            fi
        done
        run_runtime_command "$VCONTAINER_RUNTIME_CMD rm ${COMMAND_ARGS[*]}"
        ;;

    logs)
        # View container logs
        run_runtime_command "$VCONTAINER_RUNTIME_CMD logs ${COMMAND_ARGS[*]}"
        ;;

    inspect)
        # Inspect container or image
        run_runtime_command "$VCONTAINER_RUNTIME_CMD inspect ${COMMAND_ARGS[*]}"
        ;;

    start|restart|kill|pause|unpause)
        # Container state commands (no special handling needed)
        run_runtime_command "$VCONTAINER_RUNTIME_CMD $COMMAND ${COMMAND_ARGS[*]}"
        ;;

    stop)
        # Stop container and cleanup any registered port forwards
        if [ ${#COMMAND_ARGS[@]} -ge 1 ]; then
            STOP_CONTAINER_NAME="${COMMAND_ARGS[0]}"
            # Remove port forwards for this container (if any)
            if daemon_is_running; then
                unregister_port_forwards "$STOP_CONTAINER_NAME"
            fi
        fi
        run_runtime_command "$VCONTAINER_RUNTIME_CMD stop ${COMMAND_ARGS[*]}"
        ;;

    # Image commands
    commit)
        # Commit container to image
        run_runtime_command "$VCONTAINER_RUNTIME_CMD commit ${COMMAND_ARGS[*]}"
        ;;

    history)
        # Show image history
        run_runtime_command "$VCONTAINER_RUNTIME_CMD history ${COMMAND_ARGS[*]}"
        ;;

    # Registry commands
    push)
        # Push image to registry
        run_runtime_command "$VCONTAINER_RUNTIME_CMD push ${COMMAND_ARGS[*]}"
        ;;

    search)
        # Search registries
        run_runtime_command "$VCONTAINER_RUNTIME_CMD search ${COMMAND_ARGS[*]}"
        ;;

    login)
        # Login to registry - may need credentials via stdin
        # For non-interactive: runtime login -u user -p pass registry
        run_runtime_command "$VCONTAINER_RUNTIME_CMD login ${COMMAND_ARGS[*]}"
        ;;

    logout)
        # Logout from registry
        run_runtime_command "$VCONTAINER_RUNTIME_CMD logout ${COMMAND_ARGS[*]}"
        ;;

    # Runtime exec - execute command in running container
    exec)
        if [ ${#COMMAND_ARGS[@]} -lt 2 ]; then
            echo -e "${RED}[$VCONTAINER_RUNTIME_NAME]${NC} exec requires <container> <command>" >&2
            exit 1
        fi

        # Check for interactive flags
        EXEC_INTERACTIVE=false
        EXEC_ARGS=()
        for arg in "${COMMAND_ARGS[@]}"; do
            case "$arg" in
                -it|-ti|--interactive|--tty)
                    EXEC_INTERACTIVE=true
                    EXEC_ARGS+=("$arg")
                    ;;
                -i|-t)
                    EXEC_INTERACTIVE=true
                    EXEC_ARGS+=("$arg")
                    ;;
                *)
                    EXEC_ARGS+=("$arg")
                    ;;
            esac
        done

        if [ "$EXEC_INTERACTIVE" = "true" ]; then
            # Interactive exec can use daemon_interactive if daemon is running
            if daemon_is_running; then
                # Use daemon interactive mode - keeps daemon running
                [ "$VERBOSE" = "true" ] && echo -e "${CYAN}[$VCONTAINER_RUNTIME_NAME]${NC} Using daemon interactive mode" >&2
                RUNNER_ARGS=$(build_runner_args)
                "$RUNNER" $RUNNER_ARGS --daemon-interactive -- "$VCONTAINER_RUNTIME_CMD exec ${EXEC_ARGS[*]}"
            else
                # No daemon running, use regular QEMU
                RUNNER_ARGS=$(build_runner_args)
                "$RUNNER" $RUNNER_ARGS -- "$VCONTAINER_RUNTIME_CMD exec ${EXEC_ARGS[*]}"
            fi
        else
            # Non-interactive exec via daemon
            run_runtime_command "$VCONTAINER_RUNTIME_CMD exec ${EXEC_ARGS[*]}"
        fi
        ;;

    # VM shell - interactive shell into the VM itself (not a container)
    vshell)
        # Opens a shell directly in the vdkr/vpdmn VM for debugging
        # This runs /bin/sh in the VM, not inside a container
        # Useful for:
        #   - Running docker commands directly to see full error output
        #   - Debugging VM-level issues
        #   - Inspecting the VM filesystem
        if ! daemon_is_running; then
            echo -e "${RED}[$VCONTAINER_RUNTIME_NAME]${NC} vshell requires daemon mode" >&2
            echo "Start daemon with: $VCONTAINER_RUNTIME_NAME vmemres start" >&2
            exit 1
        fi

        echo -e "${CYAN}[$VCONTAINER_RUNTIME_NAME]${NC} Opening VM shell (type 'exit' to return)..." >&2
        RUNNER_ARGS=$(build_runner_args)
        "$RUNNER" $RUNNER_ARGS --daemon-interactive -- "/bin/sh"
        ;;

    # Runtime cp - copy files to/from container
    cp)
        if [ ${#COMMAND_ARGS[@]} -lt 2 ]; then
            echo -e "${RED}[$VCONTAINER_RUNTIME_NAME]${NC} cp requires <src> <dest>" >&2
            echo "Usage: $VCONTAINER_RUNTIME_NAME cp <container>:<path> <local_path>" >&2
            echo "       $VCONTAINER_RUNTIME_NAME cp <local_path> <container>:<path>" >&2
            exit 1
        fi

        SRC="${COMMAND_ARGS[0]}"
        DEST="${COMMAND_ARGS[1]}"

        # Determine direction: host->container or container->host
        if [[ "$SRC" == *":"* ]] && [[ "$DEST" != *":"* ]]; then
            # Container to host: runtime cp container:/path /local/path
            # Run runtime cp to /mnt/share, then copy from share to host
            CONTAINER_PATH="$SRC"
            HOST_PATH="$DEST"
            SHARE_DIR="${STATE_DIR:-$DEFAULT_STATE_DIR/$TARGET_ARCH}/share"

            if daemon_is_running; then
                rm -rf "$SHARE_DIR"/* 2>/dev/null || true
                run_runtime_command "$VCONTAINER_RUNTIME_CMD cp $CONTAINER_PATH /mnt/share/"
                # Find what was copied and move to destination
                if [ -n "$(ls -A "$SHARE_DIR" 2>/dev/null)" ]; then
                    cp -r "$SHARE_DIR"/* "$HOST_PATH" 2>/dev/null || cp -r "$SHARE_DIR"/* "$(dirname "$HOST_PATH")/"
                    rm -rf "$SHARE_DIR"/*
                    echo -e "${GREEN}[$VCONTAINER_RUNTIME_NAME]${NC} Copied to $HOST_PATH"
                else
                    echo -e "${RED}[$VCONTAINER_RUNTIME_NAME]${NC} Copy failed - no files in share directory" >&2
                    exit 1
                fi
            else
                echo -e "${RED}[$VCONTAINER_RUNTIME_NAME]${NC} cp requires daemon mode. Start with: $VCONTAINER_RUNTIME_NAME memres start" >&2
                exit 1
            fi

        elif [[ "$SRC" != *":"* ]] && [[ "$DEST" == *":"* ]]; then
            # Host to container: runtime cp /local/path container:/path
            HOST_PATH="$SRC"
            CONTAINER_PATH="$DEST"

            if [ ! -e "$HOST_PATH" ]; then
                echo -e "${RED}[$VCONTAINER_RUNTIME_NAME]${NC} Source not found: $HOST_PATH" >&2
                exit 1
            fi

            if daemon_is_running; then
                SHARE_DIR="${STATE_DIR:-$DEFAULT_STATE_DIR/$TARGET_ARCH}/share"
                rm -rf "$SHARE_DIR"/* 2>/dev/null || true
                cp -r "$HOST_PATH" "$SHARE_DIR/"
                sync
                BASENAME=$(basename "$HOST_PATH")
                run_runtime_command "$VCONTAINER_RUNTIME_CMD cp /mnt/share/$BASENAME $CONTAINER_PATH"
                rm -rf "$SHARE_DIR"/*
                echo -e "${GREEN}[$VCONTAINER_RUNTIME_NAME]${NC} Copied to $CONTAINER_PATH"
            else
                echo -e "${RED}[$VCONTAINER_RUNTIME_NAME]${NC} cp requires daemon mode. Start with: $VCONTAINER_RUNTIME_NAME memres start" >&2
                exit 1
            fi
        else
            echo -e "${RED}[$VCONTAINER_RUNTIME_NAME]${NC} Invalid cp syntax. One path must be container:path" >&2
            exit 1
        fi
        ;;

    vconfig)
        # Configuration management (runs on host, not in VM)
        VALID_KEYS="arch timeout state-dir verbose idle-timeout auto-daemon registry"

        if [ ${#COMMAND_ARGS[@]} -lt 1 ]; then
            # Show all config
            echo "$VCONTAINER_RUNTIME_NAME configuration ($CONFIG_FILE):"
            echo ""
            for key in $VALID_KEYS; do
                value=$(config_get "$key" "")
                default=$(config_default "$key")
                if [ -n "$value" ]; then
                    echo "  ${CYAN}$key${NC} = $value"
                else
                    echo "  ${CYAN}$key${NC} = $default ${YELLOW}(default)${NC}"
                fi
            done
            echo ""
            echo "Config directory: $CONFIG_DIR"
        else
            KEY="${COMMAND_ARGS[0]}"
            VALUE="${COMMAND_ARGS[1]:-}"

            # Validate key
            if ! echo "$VALID_KEYS" | grep -qw "$KEY"; then
                echo -e "${RED}[$VCONTAINER_RUNTIME_NAME]${NC} Unknown config key: $KEY" >&2
                echo "Valid keys: $VALID_KEYS" >&2
                exit 1
            fi

            if [ -z "$VALUE" ]; then
                # Get value
                current=$(config_get "$KEY" "")
                default=$(config_default "$KEY")
                if [ -n "$current" ]; then
                    echo "$current"
                else
                    echo "$default"
                fi
            elif [ "$VALUE" = "--reset" ]; then
                # Reset to default
                config_unset "$KEY"
                echo "Reset $KEY to default: $(config_default "$KEY")"
            else
                # Validate value for arch
                if [ "$KEY" = "arch" ]; then
                    case "$VALUE" in
                        aarch64|x86_64) ;;
                        *)
                            echo -e "${RED}[$VCONTAINER_RUNTIME_NAME]${NC} Invalid architecture: $VALUE" >&2
                            echo "Valid values: aarch64, x86_64" >&2
                            exit 1
                            ;;
                    esac
                fi

                # Validate value for verbose
                if [ "$KEY" = "verbose" ]; then
                    case "$VALUE" in
                        true|false) ;;
                        *)
                            echo -e "${RED}[$VCONTAINER_RUNTIME_NAME]${NC} Invalid verbose value: $VALUE" >&2
                            echo "Valid values: true, false" >&2
                            exit 1
                            ;;
                    esac
                fi

                # Set value
                config_set "$KEY" "$VALUE"
                echo "Set $KEY = $VALUE"
            fi
        fi
        ;;

    clean)
        # DEPRECATED: Use 'vstorage clean' instead
        echo -e "${YELLOW}[$VCONTAINER_RUNTIME_NAME]${NC} DEPRECATED: 'clean' is deprecated, use 'vstorage clean' instead" >&2
        echo -e "${YELLOW}[$VCONTAINER_RUNTIME_NAME]${NC}   vstorage clean          - clean current architecture" >&2
        echo -e "${YELLOW}[$VCONTAINER_RUNTIME_NAME]${NC}   vstorage clean <arch>   - clean specific architecture" >&2
        echo -e "${YELLOW}[$VCONTAINER_RUNTIME_NAME]${NC}   vstorage clean --all    - clean all architectures" >&2
        echo "" >&2

        # Still perform the clean for now (will be removed in future version)
        CLEAN_DIR="${STATE_DIR:-$DEFAULT_STATE_DIR/$TARGET_ARCH}"
        if [ -d "$CLEAN_DIR" ]; then
            # Stop memres if running
            if [ -f "$CLEAN_DIR/daemon.pid" ]; then
                pid=$(cat "$CLEAN_DIR/daemon.pid" 2>/dev/null)
                if [ -n "$pid" ] && [ -d "/proc/$pid" ]; then
                    echo -e "${YELLOW}[$VCONTAINER_RUNTIME_NAME]${NC} Stopping memres (PID $pid)..."
                    kill "$pid" 2>/dev/null || true
                fi
            fi
            echo -e "${YELLOW}[$VCONTAINER_RUNTIME_NAME]${NC} Removing state directory: $CLEAN_DIR"
            rm -rf "$CLEAN_DIR"
            echo -e "${GREEN}[$VCONTAINER_RUNTIME_NAME]${NC} State cleaned. Next run will start fresh."
        else
            echo -e "${GREEN}[$VCONTAINER_RUNTIME_NAME]${NC} No state directory found for $TARGET_ARCH"
        fi
        ;;

    info)
        run_runtime_command "$VCONTAINER_RUNTIME_CMD info"
        ;;

    version)
        run_runtime_command "$VCONTAINER_RUNTIME_CMD version"
        ;;

    system)
        # Passthrough to runtime system commands (df, prune, events, etc.)
        if [ ${#COMMAND_ARGS[@]} -lt 1 ]; then
            echo -e "${RED}[$VCONTAINER_RUNTIME_NAME]${NC} system requires a subcommand: df, prune, events, info" >&2
            exit 1
        fi
        run_runtime_command "$VCONTAINER_RUNTIME_CMD system ${COMMAND_ARGS[*]}"
        ;;

    vstorage)
        # Host-side storage management (runs on host, not in VM)
        if [ ${#COMMAND_ARGS[@]} -lt 1 ]; then
            STORAGE_CMD="list"
        else
            STORAGE_CMD="${COMMAND_ARGS[0]}"
        fi

        case "$STORAGE_CMD" in
            list)
                echo "$VCONTAINER_RUNTIME_NAME storage directories:"
                echo ""
                found=0
                for state_dir in "$DEFAULT_STATE_DIR"/*/; do
                    [ -d "$state_dir" ] || continue
                    found=1
                    instance=$(basename "$state_dir")
                    size=$(du -sh "$state_dir" 2>/dev/null | cut -f1)

                    echo "  ${CYAN}$instance${NC}"
                    echo "    Path:   $state_dir"
                    echo "    Size:   $size"

                    # Check if memres is running
                    if [ -f "$state_dir/daemon.pid" ]; then
                        pid=$(cat "$state_dir/daemon.pid")
                        if [ -d "/proc/$pid" ]; then
                            echo "    Status: ${GREEN}memres running${NC} (PID $pid)"
                        else
                            echo "    Status: stopped"
                        fi
                    else
                        echo "    Status: no memres"
                    fi
                    echo ""
                done
                if [ $found -eq 0 ]; then
                    echo "  (no storage directories found)"
                    echo ""
                fi

                # Total size
                if [ -d "$DEFAULT_STATE_DIR" ] && [ $found -gt 0 ]; then
                    total=$(du -sh "$DEFAULT_STATE_DIR" 2>/dev/null | cut -f1)
                    echo "Total: $total"
                fi
                ;;

            path)
                # Show path for specific or current architecture
                arch="${COMMAND_ARGS[1]:-$TARGET_ARCH}"
                echo "${STATE_DIR:-$DEFAULT_STATE_DIR/$arch}"
                ;;

            df)
                # Detailed breakdown
                for state_dir in "$DEFAULT_STATE_DIR"/*/; do
                    [ -d "$state_dir" ] || continue
                    instance=$(basename "$state_dir")
                    echo "${BOLD}$instance${NC}:"

                    # Show individual components
                    for item in "$VCONTAINER_STATE_FILE" share; do
                        if [ -e "$state_dir/$item" ]; then
                            item_size=$(du -sh "$state_dir/$item" 2>/dev/null | cut -f1)
                            printf "  %-20s %s\n" "$item" "$item_size"
                        fi
                    done
                    echo ""
                done
                ;;

            clean)
                # Clean storage for specific arch or all
                arch="${COMMAND_ARGS[1]:-}"
                if [ "$arch" = "--all" ]; then
                    # Stop any running memres first
                    for pid_file in "$DEFAULT_STATE_DIR"/*/daemon.pid; do
                        [ -f "$pid_file" ] || continue
                        pid=$(cat "$pid_file" 2>/dev/null)
                        if [ -n "$pid" ] && [ -d "/proc/$pid" ]; then
                            echo -e "${YELLOW}[$VCONTAINER_RUNTIME_NAME]${NC} Stopping memres (PID $pid)..."
                            kill "$pid" 2>/dev/null || true
                        fi
                    done
                    echo -e "${YELLOW}[$VCONTAINER_RUNTIME_NAME]${NC} Removing all storage directories..."
                    rm -rf "$DEFAULT_STATE_DIR"
                    echo -e "${GREEN}[$VCONTAINER_RUNTIME_NAME]${NC} All storage cleaned."
                elif [ -n "$arch" ]; then
                    # Clean specific arch
                    clean_dir="$DEFAULT_STATE_DIR/$arch"
                    if [ -d "$clean_dir" ]; then
                        # Stop memres if running
                        if [ -f "$clean_dir/daemon.pid" ]; then
                            pid=$(cat "$clean_dir/daemon.pid" 2>/dev/null)
                            if [ -n "$pid" ] && [ -d "/proc/$pid" ]; then
                                echo -e "${YELLOW}[$VCONTAINER_RUNTIME_NAME]${NC} Stopping memres (PID $pid)..."
                                kill "$pid" 2>/dev/null || true
                            fi
                        fi
                        rm -rf "$clean_dir"
                        echo -e "${GREEN}[$VCONTAINER_RUNTIME_NAME]${NC} Cleaned: $clean_dir"
                    else
                        echo -e "${YELLOW}[$VCONTAINER_RUNTIME_NAME]${NC} Not found: $clean_dir"
                    fi
                else
                    # Clean current arch (same as existing clean command)
                    clean_dir="${STATE_DIR:-$DEFAULT_STATE_DIR/$TARGET_ARCH}"
                    if [ -d "$clean_dir" ]; then
                        # Stop memres if running
                        if [ -f "$clean_dir/daemon.pid" ]; then
                            pid=$(cat "$clean_dir/daemon.pid" 2>/dev/null)
                            if [ -n "$pid" ] && [ -d "/proc/$pid" ]; then
                                echo -e "${YELLOW}[$VCONTAINER_RUNTIME_NAME]${NC} Stopping memres (PID $pid)..."
                                kill "$pid" 2>/dev/null || true
                            fi
                        fi
                        rm -rf "$clean_dir"
                        echo -e "${GREEN}[$VCONTAINER_RUNTIME_NAME]${NC} Cleaned: $clean_dir"
                    else
                        echo -e "${GREEN}[$VCONTAINER_RUNTIME_NAME]${NC} No storage directory found for $TARGET_ARCH"
                    fi
                fi
                ;;

            *)
                echo -e "${RED}[$VCONTAINER_RUNTIME_NAME]${NC} Unknown vstorage subcommand: $STORAGE_CMD" >&2
                echo "Usage: $VCONTAINER_RUNTIME_NAME vstorage [list|path|df|clean]" >&2
                echo "" >&2
                echo "Subcommands:" >&2
                echo "  list              List all storage directories with details" >&2
                echo "  path [arch]       Show path to storage directory" >&2
                echo "  df                Show detailed disk usage breakdown" >&2
                echo "  clean [arch|--all] Clean storage directories" >&2
                exit 1
                ;;
        esac
        ;;

    vrun)
        # Extended run: run a command in a container (runtime-like syntax)
        # Usage: <tool> vrun [options] <image> [command] [args...]
        # Options:
        #   -it, -i, -t              Interactive mode with TTY
        #   --network, -n            Enable networking
        #   -p <host>:<guest>        Forward port from host to container
        #   -v <host>:<container>    Mount host directory in container
        #
        # Parse vrun-specific options (allows runtime-like: vdkr vrun -it alpine /bin/sh)
        VRUN_VOLUMES=()
        HAS_VOLUMES=false

        while [ ${#COMMAND_ARGS[@]} -gt 0 ]; do
            case "${COMMAND_ARGS[0]}" in
                -it|--interactive)
                    INTERACTIVE="true"
                    COMMAND_ARGS=("${COMMAND_ARGS[@]:1}")
                    ;;
                -i|-t)
                    INTERACTIVE="true"
                    COMMAND_ARGS=("${COMMAND_ARGS[@]:1}")
                    ;;
                --no-network)
                    NETWORK="false"
                    COMMAND_ARGS=("${COMMAND_ARGS[@]:1}")
                    ;;
                -p|--publish)
                    # Port forward: -p 8080:80 or -p 8080:80/tcp
                    NETWORK="true"  # Port forwarding requires networking
                    if [ ${#COMMAND_ARGS[@]} -lt 2 ]; then
                        echo -e "${RED}[$VCONTAINER_RUNTIME_NAME]${NC} -p requires <host_port>:<container_port>" >&2
                        exit 1
                    fi
                    PORT_FORWARDS+=("${COMMAND_ARGS[1]}")
                    COMMAND_ARGS=("${COMMAND_ARGS[@]:2}")
                    ;;
                -v|--volume)
                    # Volume mount: -v /host/path:/container/path[:ro|:rw]
                    if [ ${#COMMAND_ARGS[@]} -lt 2 ]; then
                        echo -e "${RED}[$VCONTAINER_RUNTIME_NAME]${NC} -v requires <host_path>:<container_path>" >&2
                        exit 1
                    fi
                    VRUN_VOLUMES+=("-v" "${COMMAND_ARGS[1]}")
                    HAS_VOLUMES=true
                    COMMAND_ARGS=("${COMMAND_ARGS[@]:2}")
                    ;;
                -*)
                    # Unknown option - stop parsing, rest goes to container
                    break
                    ;;
                *)
                    # Not an option - this is the image name
                    break
                    ;;
            esac
        done

        # Volume mounts require daemon mode
        if [ "$HAS_VOLUMES" = "true" ] && ! daemon_is_running; then
            echo -e "${RED}[$VCONTAINER_RUNTIME_NAME]${NC} Volume mounts require daemon mode. Start with: $VCONTAINER_RUNTIME_NAME memres start" >&2
            exit 1
        fi

        if [ ${#COMMAND_ARGS[@]} -lt 1 ]; then
            echo -e "${RED}[$VCONTAINER_RUNTIME_NAME]${NC} vrun requires <image> [command] [args...]" >&2
            echo "Usage: $VCONTAINER_RUNTIME_NAME vrun [options] <image> [command] [args...]" >&2
            echo "" >&2
            echo "Options:" >&2
            echo "  -it, -i, -t              Interactive mode with TTY" >&2
            echo "  --no-network             Disable networking" >&2
            echo "  -p <host>:<container>    Forward port" >&2
            echo "  -v <host>:<container>    Mount host directory in container" >&2
            echo "" >&2
            echo "Examples:" >&2
            echo "  $VCONTAINER_RUNTIME_NAME vrun alpine /bin/ls -la" >&2
            echo "  $VCONTAINER_RUNTIME_NAME vrun -it alpine /bin/sh" >&2
            echo "  $VCONTAINER_RUNTIME_NAME vrun -p 8080:80 nginx:latest" >&2
            echo "  $VCONTAINER_RUNTIME_NAME vrun -v /tmp/data:/data alpine cat /data/file.txt" >&2
            exit 1
        fi

        IMAGE_NAME="${COMMAND_ARGS[0]}"
        CONTAINER_CMD=""

        # Build command from remaining args
        for ((i=1; i<${#COMMAND_ARGS[@]}; i++)); do
            if [ -n "$CONTAINER_CMD" ]; then
                CONTAINER_CMD="$CONTAINER_CMD ${COMMAND_ARGS[$i]}"
            else
                CONTAINER_CMD="${COMMAND_ARGS[$i]}"
            fi
        done

        # Prepare volume mounts if any
        VOLUME_OPTS=""
        if [ "$HAS_VOLUMES" = "true" ]; then
            [ "$VERBOSE" = "true" ] && echo -e "${CYAN}[$VCONTAINER_RUNTIME_NAME]${NC} Preparing volume mounts..." >&2

            # Parse and prepare volumes (transforms host paths to guest paths)
            parse_and_prepare_volumes "${VRUN_VOLUMES[@]}" || {
                cleanup_volumes
                exit 1
            }

            # Build volume options string from transformed args
            VOLUME_OPTS="${TRANSFORMED_VOLUME_ARGS[*]}"
            [ "$VERBOSE" = "true" ] && echo -e "${CYAN}[$VCONTAINER_RUNTIME_NAME]${NC} Volume options: $VOLUME_OPTS" >&2
        fi

        # Build runtime run command
        RUNTIME_RUN_OPTS="--rm"
        if [ "$INTERACTIVE" = "true" ]; then
            RUNTIME_RUN_OPTS="$RUNTIME_RUN_OPTS -it"
        fi
        # Use bridge networking (Docker's default) with VM's DNS
        # Each container gets its own IP on 172.17.0.0/16
        if [ "$NETWORK" = "true" ]; then
            RUNTIME_RUN_OPTS="$RUNTIME_RUN_OPTS --dns=10.0.2.3 --dns=8.8.8.8"
        fi

        # Add volume mounts
        if [ -n "$VOLUME_OPTS" ]; then
            RUNTIME_RUN_OPTS="$RUNTIME_RUN_OPTS $VOLUME_OPTS"
        fi

        if [ -n "$CONTAINER_CMD" ]; then
            # Clear entrypoint when command provided - ensures command runs directly
            # without being passed to image's entrypoint (e.g., prevents 'sh /bin/echo')
            RUNTIME_CMD="$VCONTAINER_RUNTIME_CMD run $RUNTIME_RUN_OPTS --entrypoint '' $IMAGE_NAME $CONTAINER_CMD"
        else
            RUNTIME_CMD="$VCONTAINER_RUNTIME_CMD run $RUNTIME_RUN_OPTS $IMAGE_NAME"
        fi

        [ "$VERBOSE" = "true" ] && echo -e "${CYAN}[$VCONTAINER_RUNTIME_NAME]${NC} Runtime command: $RUNTIME_CMD" >&2

        # Use daemon mode for non-interactive runs
        if [ "$INTERACTIVE" = "true" ]; then
            # Interactive mode with volumes still needs to stop daemon (volumes use share dir)
            # Interactive mode without volumes can use daemon_interactive (faster)
            if [ "$HAS_VOLUMES" = "false" ] && daemon_is_running; then
                # Use daemon interactive mode - keeps daemon running
                [ "$VERBOSE" = "true" ] && echo -e "${CYAN}[$VCONTAINER_RUNTIME_NAME]${NC} Using daemon interactive mode" >&2
                RUNNER_ARGS=$(build_runner_args)
                "$RUNNER" $RUNNER_ARGS --daemon-interactive -- "$RUNTIME_CMD"
                exit $?
            else
                # Fall back to regular QEMU for interactive (stop daemon if running)
                DAEMON_WAS_RUNNING=false
                if daemon_is_running; then
                    DAEMON_WAS_RUNNING=true
                    echo -e "${YELLOW}[$VCONTAINER_RUNTIME_NAME]${NC} Stopping daemon for interactive mode..." >&2
                    "$RUNNER" --state-dir "${STATE_DIR:-$DEFAULT_STATE_DIR/$TARGET_ARCH}" --daemon-stop >/dev/null 2>&1 || true
                    sleep 1
                fi
                RUNNER_ARGS=$(build_runner_args)
                "$RUNNER" $RUNNER_ARGS -- "$RUNTIME_CMD"
                VRUN_EXIT=$?

                # Sync volumes back after container exits
                if [ "$HAS_VOLUMES" = "true" ]; then
                    sync_volumes_back
                    cleanup_volumes
                fi

                # Restart daemon if it was running before
                if [ "$DAEMON_WAS_RUNNING" = "true" ]; then
                    echo -e "${CYAN}[$VCONTAINER_RUNTIME_NAME]${NC} Restarting daemon..." >&2
                    "$RUNNER" $RUNNER_ARGS --daemon-start >/dev/null 2>&1 || true
                fi

                exit $VRUN_EXIT
            fi
        else
            # Non-interactive can use daemon mode
            run_runtime_command "$RUNTIME_CMD"
            VRUN_EXIT=$?

            # Sync volumes back after container exits
            if [ "$HAS_VOLUMES" = "true" ]; then
                sync_volumes_back
                cleanup_volumes
            fi

            exit $VRUN_EXIT
        fi
        ;;

    run)
        # Runtime run command - mirrors 'docker/podman run' syntax
        # Usage: <tool> run [options] <image> [command]
        # Automatically prepends 'runtime run' to the arguments
        # Supports volume mounts with -v (requires daemon mode)
        #
        # NOTE: --network=host is added by default because Docker runs with
        # --bridge=none inside the VM. Users can override with --network=none
        # if they truly want no networking.
        if [ ${#COMMAND_ARGS[@]} -eq 0 ]; then
            echo -e "${RED}[$VCONTAINER_RUNTIME_NAME]${NC} run requires an image" >&2
            echo "Usage: $VCONTAINER_RUNTIME_NAME run [options] <image> [command]" >&2
            echo "" >&2
            echo "Examples:" >&2
            echo "  $VCONTAINER_RUNTIME_NAME run alpine /bin/echo hello" >&2
            echo "  $VCONTAINER_RUNTIME_NAME run -it alpine /bin/sh" >&2
            echo "  $VCONTAINER_RUNTIME_NAME run --rm -e FOO=bar myapp:latest" >&2
            echo "  $VCONTAINER_RUNTIME_NAME run -v /tmp/data:/data alpine cat /data/file.txt" >&2
            exit 1
        fi

        # Check if any volume mounts, network, port forwards, or detach are present
        RUN_HAS_VOLUMES=false
        RUN_HAS_NETWORK=false
        RUN_HAS_PORT_FORWARDS=false
        RUN_IS_DETACHED=false
        RUN_CONTAINER_NAME=""
        RUN_PORT_FORWARDS=()

        # Parse COMMAND_ARGS to extract relevant flags
        i=0
        prev_arg=""
        for arg in "${COMMAND_ARGS[@]}"; do
            case "$arg" in
                -v|--volume)
                    RUN_HAS_VOLUMES=true
                    ;;
                --network=*|--net=*)
                    RUN_HAS_NETWORK=true
                    ;;
                -p|--publish)
                    RUN_HAS_PORT_FORWARDS=true
                    ;;
                -d|--detach)
                    RUN_IS_DETACHED=true
                    ;;
                --name=*)
                    RUN_CONTAINER_NAME="${arg#--name=}"
                    ;;
            esac
            # Check if previous arg was -p or --publish
            if [ "$prev_arg" = "-p" ] || [ "$prev_arg" = "--publish" ]; then
                # arg is the port specification (e.g., 8080:80)
                RUN_PORT_FORWARDS+=("$arg")
            fi
            # Check if previous arg was --name
            if [ "$prev_arg" = "--name" ]; then
                RUN_CONTAINER_NAME="$arg"
            fi
            prev_arg="$arg"
            i=$((i + 1))
        done

        # Volume mounts require daemon mode
        if [ "$RUN_HAS_VOLUMES" = "true" ] && ! daemon_is_running; then
            echo -e "${RED}[$VCONTAINER_RUNTIME_NAME]${NC} Volume mounts require daemon mode. Start with: $VCONTAINER_RUNTIME_NAME memres start" >&2
            exit 1
        fi

        # Transform volume mounts if present
        if [ "$RUN_HAS_VOLUMES" = "true" ]; then
            [ "$VERBOSE" = "true" ] && echo -e "${CYAN}[$VCONTAINER_RUNTIME_NAME]${NC} Preparing volume mounts for run command..." >&2

            # Parse and prepare volumes (transforms host paths to guest paths)
            parse_and_prepare_volumes "${COMMAND_ARGS[@]}" || {
                cleanup_volumes
                exit 1
            }
            # Update COMMAND_ARGS with transformed values
            COMMAND_ARGS=("${TRANSFORMED_VOLUME_ARGS[@]}")
        fi

        # Build runtime run command from args
        # Note: -it may have been consumed by global parser, so add it back if INTERACTIVE is set
        # Use bridge networking (Docker's default) - each container gets 172.17.0.x IP
        # User can override with --network=host for legacy behavior
        RUN_NETWORK_OPTS=""
        if [ "$RUN_HAS_NETWORK" = "false" ]; then
            RUN_NETWORK_OPTS="--dns=10.0.2.3 --dns=8.8.8.8"
            [ "$VERBOSE" = "true" ] && echo -e "${CYAN}[$VCONTAINER_RUNTIME_NAME]${NC} Using default bridge networking" >&2
        fi

        if [ "$INTERACTIVE" = "true" ]; then
            RUNTIME_CMD="$VCONTAINER_RUNTIME_CMD run -it $RUN_NETWORK_OPTS ${COMMAND_ARGS[*]}"
        else
            RUNTIME_CMD="$VCONTAINER_RUNTIME_CMD run $RUN_NETWORK_OPTS ${COMMAND_ARGS[*]}"
        fi

        if [ "$INTERACTIVE" = "true" ]; then
            # Interactive mode with volumes still needs to stop daemon (volumes use share dir)
            # Interactive mode without volumes can use daemon_interactive (faster)
            if [ "$RUN_HAS_VOLUMES" = "false" ] && daemon_is_running; then
                # Use daemon interactive mode - keeps daemon running
                [ "$VERBOSE" = "true" ] && echo -e "${CYAN}[$VCONTAINER_RUNTIME_NAME]${NC} Using daemon interactive mode" >&2
                RUNNER_ARGS=$(build_runner_args)
                "$RUNNER" $RUNNER_ARGS --daemon-interactive -- "$RUNTIME_CMD"
                exit $?
            else
                # Fall back to regular QEMU for interactive (stop daemon if running)
                DAEMON_WAS_RUNNING=false
                if daemon_is_running; then
                    DAEMON_WAS_RUNNING=true
                    echo -e "${YELLOW}[$VCONTAINER_RUNTIME_NAME]${NC} Stopping daemon for interactive mode..." >&2
                    "$RUNNER" --state-dir "${STATE_DIR:-$DEFAULT_STATE_DIR/$TARGET_ARCH}" --daemon-stop >/dev/null 2>&1 || true
                    sleep 1
                fi
                RUNNER_ARGS=$(build_runner_args)
                "$RUNNER" $RUNNER_ARGS -- "$RUNTIME_CMD"
                RUN_EXIT=$?

                # Sync volumes back after container exits
                if [ "$RUN_HAS_VOLUMES" = "true" ]; then
                    sync_volumes_back
                    cleanup_volumes
                fi

                # Restart daemon if it was running before
                if [ "$DAEMON_WAS_RUNNING" = "true" ]; then
                    echo -e "${CYAN}[$VCONTAINER_RUNTIME_NAME]${NC} Restarting daemon..." >&2
                    "$RUNNER" $RUNNER_ARGS --daemon-start >/dev/null 2>&1 || true
                fi

                exit $RUN_EXIT
            fi
        else
            # Non-interactive - use daemon mode when available

            # For detached containers with port forwards, add them dynamically via QMP
            if [ "$RUN_IS_DETACHED" = "true" ] && [ "$RUN_HAS_PORT_FORWARDS" = "true" ] && daemon_is_running; then
                # Generate container name if not provided (needed for port tracking)
                if [ -z "$RUN_CONTAINER_NAME" ]; then
                    # Generate a random name like docker does
                    RUN_CONTAINER_NAME="$(cat /proc/sys/kernel/random/uuid | cut -c1-12)"
                    # Update COMMAND_ARGS to include the generated name
                    RUNTIME_CMD="$VCONTAINER_RUNTIME_CMD run --name=$RUN_CONTAINER_NAME $RUN_NETWORK_OPTS ${COMMAND_ARGS[*]}"
                fi

                # Add port forwards via QMP and register them
                for port_spec in "${RUN_PORT_FORWARDS[@]}"; do
                    # Parse port specification: [host_ip:]host_port:container_port[/protocol]
                    # Examples: 8080:80, 127.0.0.1:8080:80, 8080:80/tcp, 8080:80/udp
                    spec="$port_spec"
                    protocol="tcp"
                    host_port=""
                    guest_port=""

                    # Extract protocol if present
                    if echo "$spec" | grep -q '/'; then
                        protocol="${spec##*/}"
                        spec="${spec%/*}"
                    fi

                    # Count colons to determine format
                    colon_count=$(echo "$spec" | tr -cd ':' | wc -c)
                    if [ "$colon_count" -eq 2 ]; then
                        # Format: host_ip:host_port:container_port (ignore host_ip for now)
                        host_port=$(echo "$spec" | cut -d: -f2)
                        guest_port=$(echo "$spec" | cut -d: -f3)
                    else
                        # Format: host_port:container_port
                        host_port=$(echo "$spec" | cut -d: -f1)
                        guest_port=$(echo "$spec" | cut -d: -f2)
                    fi

                    if [ -n "$host_port" ] && [ -n "$guest_port" ]; then
                        if qmp_add_hostfwd "$host_port" "$guest_port" "$protocol"; then
                            register_port_forward "$RUN_CONTAINER_NAME" "$host_port" "$guest_port" "$protocol"
                        else
                            echo -e "${YELLOW}[$VCONTAINER_RUNTIME_NAME]${NC} Warning: Could not add port forward ${host_port}:${guest_port}" >&2
                        fi
                    fi
                done
            fi

            run_runtime_command "$RUNTIME_CMD"
            RUN_EXIT=$?

            # Sync volumes back after container exits
            if [ "$RUN_HAS_VOLUMES" = "true" ]; then
                sync_volumes_back
                cleanup_volumes
            fi

            exit $RUN_EXIT
        fi
        ;;

    # Memory resident subcommand: <tool> memres start|stop|restart|status
    # vmemres is the preferred name (v prefix for tool-specific commands)
    memres|vmemres)
        if [ ${#COMMAND_ARGS[@]} -lt 1 ]; then
            echo -e "${RED}[$VCONTAINER_RUNTIME_NAME]${NC} memres requires a subcommand: start, stop, restart, status, list" >&2
            exit 1
        fi

        MEMRES_CMD="${COMMAND_ARGS[0]}"

        # Parse memres-specific options (after the subcommand)
        MEMRES_ARGS=("${COMMAND_ARGS[@]:1}")
        i=0
        while [ $i -lt ${#MEMRES_ARGS[@]} ]; do
            arg="${MEMRES_ARGS[$i]}"
            case "$arg" in
                -p|--publish)
                    # Port forward: -p 8080:80 or -p 8080:80/tcp
                    i=$((i + 1))
                    if [ $i -ge ${#MEMRES_ARGS[@]} ]; then
                        echo -e "${RED}[$VCONTAINER_RUNTIME_NAME]${NC} -p requires <host_port>:<container_port>" >&2
                        exit 1
                    fi
                    PORT_FORWARDS+=("${MEMRES_ARGS[$i]}")
                    ;;
            esac
            i=$((i + 1))
        done

        RUNNER_ARGS=$(build_runner_args)

        case "$MEMRES_CMD" in
            start)
                if daemon_is_running; then
                    echo -e "${YELLOW}[$VCONTAINER_RUNTIME_NAME]${NC} A memres instance is already running for $TARGET_ARCH"
                    echo ""
                    "$RUNNER" $RUNNER_ARGS --daemon-status
                    echo ""
                    echo "Options:"
                    echo "  1) Restart with new settings (stops current instance)"
                    echo "  2) Start additional instance with different --state-dir"
                    echo "  3) Cancel"
                    echo ""
                    read -p "Choice [1-3]: " choice
                    case "$choice" in
                        1)
                            echo -e "${CYAN}[$VCONTAINER_RUNTIME_NAME]${NC} Restarting memres..."
                            "$RUNNER" $RUNNER_ARGS --daemon-stop
                            sleep 1
                            "$RUNNER" $RUNNER_ARGS --daemon-start
                            ;;
                        2)
                            echo ""
                            echo "To start an additional instance, use -I <name>:"
                            echo "  $VCONTAINER_RUNTIME_NAME -I web memres start -p 8080:80"
                            echo "  $VCONTAINER_RUNTIME_NAME -I api memres start -p 3000:3000"
                            echo ""
                            echo "Then interact with it:"
                            echo "  $VCONTAINER_RUNTIME_NAME -I web images"
                            echo ""
                            exit 0
                            ;;
                        *)
                            echo "Cancelled."
                            exit 0
                            ;;
                    esac
                else
                    "$RUNNER" $RUNNER_ARGS --daemon-start
                fi
                ;;
            stop)
                # Clear port forward registry when stopping daemon
                pf_file=$(get_port_forward_file)
                if [ -f "$pf_file" ]; then
                    rm -f "$pf_file"
                fi
                "$RUNNER" $RUNNER_ARGS --daemon-stop
                ;;
            restart)
                # Stop if running and clear port forward registry
                pf_file=$(get_port_forward_file)
                if [ -f "$pf_file" ]; then
                    rm -f "$pf_file"
                fi
                "$RUNNER" $RUNNER_ARGS --daemon-stop 2>/dev/null || true

                # Clean if --clean was passed
                for arg in "${COMMAND_ARGS[@]:1}"; do
                    if [ "$arg" = "--clean" ]; then
                        CLEAN_DIR="${STATE_DIR:-$DEFAULT_STATE_DIR/$TARGET_ARCH}"
                        if [ -d "$CLEAN_DIR" ]; then
                            echo -e "${YELLOW}[$VCONTAINER_RUNTIME_NAME]${NC} Cleaning state directory: $CLEAN_DIR"
                            rm -rf "$CLEAN_DIR"
                        fi
                        break
                    fi
                done

                # Start
                "$RUNNER" $RUNNER_ARGS --daemon-start
                ;;
            status)
                "$RUNNER" $RUNNER_ARGS --daemon-status
                ;;
            list)
                # Show all running memres instances
                echo "Running memres instances:"
                echo ""
                found=0
                tracked_pids=""
                for pid_file in "$DEFAULT_STATE_DIR"/*/daemon.pid; do
                    [ -f "$pid_file" ] || continue
                    pid=$(cat "$pid_file" 2>/dev/null)
                    if [ -n "$pid" ] && [ -d "/proc/$pid" ]; then
                        instance_dir=$(dirname "$pid_file")
                        instance_name=$(basename "$instance_dir")
                        echo "  ${CYAN}$instance_name${NC}"
                        echo "    PID: $pid"
                        echo "    State: $instance_dir"
                        if [ -f "$instance_dir/qemu.log" ]; then
                            # Try to extract port forwards from qemu command line
                            ports=$(grep -o 'hostfwd=[^,]*' "$instance_dir/qemu.log" 2>/dev/null | sed 's/hostfwd=tcp:://g; s/-/:/' | tr '\n' ' ')
                            [ -n "$ports" ] && echo "    Ports: $ports"
                        fi
                        echo ""
                        found=$((found + 1))
                        tracked_pids="$tracked_pids $pid"
                    fi
                done
                if [ $found -eq 0 ]; then
                    echo "  (none)"
                fi

                # Check for zombie/orphan QEMU processes (vdkr or vpdmn)
                echo ""
                echo "Checking for orphan QEMU processes..."
                zombies=""
                for qemu_pid in $(pgrep -f "qemu-system.*runtime=(docker|podman)" 2>/dev/null || true); do
                    # Skip if this PID is already tracked
                    if echo "$tracked_pids" | grep -qw "$qemu_pid"; then
                        continue
                    fi
                    # Also check other tool's state dirs
                    other_tracked=false
                    for vpid_file in "$OTHER_STATE_DIR"/*/daemon.pid; do
                        [ -f "$vpid_file" ] || continue
                        vpid=$(cat "$vpid_file" 2>/dev/null)
                        if [ "$vpid" = "$qemu_pid" ]; then
                            other_tracked=true
                            break
                        fi
                    done
                    if [ "$other_tracked" = "true" ]; then
                        continue
                    fi
                    zombies="$zombies $qemu_pid"
                done

                if [ -n "$zombies" ]; then
                    echo ""
                    echo -e "${YELLOW}Orphan QEMU processes found:${NC}"
                    for zpid in $zombies; do
                        # Extract runtime from cmdline
                        cmdline=$(cat /proc/$zpid/cmdline 2>/dev/null | tr '\0' ' ')
                        runtime=$(echo "$cmdline" | grep -o 'runtime=[a-z]*' | cut -d= -f2)
                        state_dir=$(echo "$cmdline" | grep -o 'path=[^,]*daemon.sock' | sed 's|path=||; s|/daemon.sock||')
                        echo ""
                        echo "  ${RED}PID $zpid${NC} (${runtime:-unknown})"
                        [ -n "$state_dir" ] && echo "    State: $state_dir"
                        echo "    Kill with: kill $zpid"
                    done
                    echo ""
                    echo -e "To kill all orphans: ${CYAN}kill$zombies${NC}"
                else
                    echo "  (no orphans found)"
                fi
                ;;
            clean-ports)
                # Clear the port forward registry without stopping daemon
                pf_file=$(get_port_forward_file)
                if [ -f "$pf_file" ]; then
                    count=$(wc -l < "$pf_file")
                    rm -f "$pf_file"
                    echo -e "${GREEN}[$VCONTAINER_RUNTIME_NAME]${NC} Cleared $count port forward entries from registry"
                else
                    echo -e "${CYAN}[$VCONTAINER_RUNTIME_NAME]${NC} Port forward registry is already empty"
                fi
                ;;
            *)
                echo -e "${RED}[$VCONTAINER_RUNTIME_NAME]${NC} Unknown memres subcommand: $MEMRES_CMD" >&2
                echo "Usage: $VCONTAINER_RUNTIME_NAME vmemres start|stop|restart|status|list|clean-ports" >&2
                exit 1
                ;;
        esac
        ;;

    *)
        echo -e "${RED}[$VCONTAINER_RUNTIME_NAME]${NC} Unknown command: $COMMAND" >&2
        echo "Run '$VCONTAINER_RUNTIME_NAME --help' for usage" >&2
        exit 1
        ;;
esac