aboutsummaryrefslogtreecommitdiffstatshomepage
diff options
context:
space:
mode:
authoronefang2021-10-08 00:23:39 +1000
committeronefang2021-10-08 00:23:39 +1000
commit13ea9a731ea344524c724a9c285fb3f8f74ae1c3 (patch)
tree0f2cc5f6167f134583a9ed9a7cea05851591a288
parentTweak the sledjchisl help text. (diff)
downloadopensim-SC-13ea9a731ea344524c724a9c285fb3f8f74ae1c3.zip
opensim-SC-13ea9a731ea344524c724a9c285fb3f8f74ae1c3.tar.gz
opensim-SC-13ea9a731ea344524c724a9c285fb3f8f74ae1c3.tar.bz2
opensim-SC-13ea9a731ea344524c724a9c285fb3f8f74ae1c3.tar.xz
Refactor GITAR, so we can call it direct from BACKUP.
-rw-r--r--src/sledjchisl/sledjchisl.c355
1 files changed, 181 insertions, 174 deletions
diff --git a/src/sledjchisl/sledjchisl.c b/src/sledjchisl/sledjchisl.c
index aca04d6..b74e419 100644
--- a/src/sledjchisl/sledjchisl.c
+++ b/src/sledjchisl/sledjchisl.c
@@ -2271,6 +2271,182 @@ static int filterARs(struct dirtree *node)
2271 return 0; 2271 return 0;
2272} 2272}
2273 2273
2274void gitar(simData *simd, char *sim, int count, int window, int panes, int pane, int m, int member, char *last)
2275{
2276 /* Work around OpenSims slow database corruption bug by using git to store all old backups.
2277 Try to squeeze every last byte out of the tarballs. Seems to cut the total storage size down to one third the size of just the raw I/OAR files.
2278 Saves even more if there's been no changes.
2279 On the other hand, these backup files will grow indefinately, the more changes, the faster it grows. I can live with that for more reliable backups that go back further.
2280 Tries to avoid loosing data if things go wrong. I think the main remaining problem would be running out of space, in which case you have bigger problems to deal with.
2281
2282 Strategy - unpack the last one, unpack and commit any old I/OARs, pack up the result, delete it's working directory, THEN run the save i/oar.
2283 Avoids having to sync with OpenSim finishing the current I/OAR, and as a bonus, an easy to deliver latest I/OAR for people that want it.
2284 */
2285 char *name = xstrdup(sim);
2286
2287 if (m)
2288 {
2289 if (member)
2290 {
2291 free(name);
2292 name = xmprintf("%s_%s", sim, last);
2293 }
2294 else
2295 {
2296 free(name);
2297 return;
2298 }
2299 }
2300
2301 char type = m ? 'I' : 'O';
2302 char *gar = xmprintf("%s-git%cAR", name, type), *gtr = xmprintf("%s/%s.tar.xz", scBackup, gar);
2303 char *dir = xmprintf("%s/temp_%c_%s", scBackup, type, name);
2304 char *glog = xmprintf("%s/log.log", scBackup), *gerr = xmprintf("%s/errors", dir);
2305 // Make sure stuff that's already compressed doesn't get compressed by git.
2306 // Also tries to protect binaries from mangling.
2307 char *gab = xmprintf("%s/%s/.gitattributes", dir, gar), *gad =
2308 "*.dat -diff -text\n"
2309 "*.bvh -delta -diff -text\n"
2310 "*.j2c -delta -diff -text\n" // OpenSim uses all four JPEG 2000 extensions. In the same directories. lol
2311 "*.jp2 -delta -diff -text\n"
2312 "*.jpc -delta -diff -text\n"
2313 "*.jpg -delta -diff -text\n"
2314 "*.llmesh -delta -diff -text\n"
2315 "*.ogg -delta -diff -text\n"
2316 "*.png -delta -diff -text\n"
2317 "*.r32 -delta -diff -text\n"
2318 "*.tga -delta -diff -text\n";
2319 ARList *ourARs = xzalloc(sizeof(ARList));
2320
2321 if (qfile_exist(dir))
2322 {
2323 if (shellMeFail("echo 'Mess left over from last backup in %d, not gonna run!' >>%s", dir, gerr)) E("Cleaning up the mess!");
2324 if (shellMeFail("mv %s/*.%car %s 2>&1 >>%s", dir, tolower(type), scBackup, gerr)) E("Failed cleaning up the mess!");
2325 goto gitARend;
2326 }
2327
2328 // Either unpack the old gitAR, or create a new one.
2329 qfile_mkdir(dir, S_IRWXU | S_IRGRP | S_IXGRP, true);
2330 if (qfile_exist(gtr))
2331 {
2332 I("Unpacking %s", gtr);
2333 if (shellMeFail("cd %s; ionice -c3 nice -n 19 tar -xf %s >>%s", dir, gtr, gerr)) E("Failed to unpack %s!", gtr);
2334 char *t = xmprintf("%s/%s_git%cAR", dir, name, type);
2335 // Changed from _ to -, but deal with old archives.
2336 if (qfile_exist(t))
2337 {
2338 if (shellMeFail("mv %s %s/%s", t, dir, gar)) E("Failed to move %s!", t);
2339 }
2340 free(t);
2341 }
2342 else
2343 {
2344 // git will create gar for us.
2345 if (shellMeFail("cd %s; git init --quiet %s >>%s", dir, gar, gerr)) E("Failed to git init %s/%s!", dir, gar);
2346
2347 // Coz git insists.
2348 if (shellMeFail("cd %s/%s; git config user.email \"opensim@$(hostname -A | cut -d ' ' -f 1)\"", dir, gar)) E("Failed to git config user.email!");
2349 if (shellMeFail("cd %s/%s; git config user.name \"opensim\"", dir, gar)) E("Failed to git config user.name!");
2350
2351 // Coz git insists on having something comitted before it can change the branch name.
2352 if (-1 == qfile_save(gab, gad, strlen(gad), false)) E("Faild to write %s file!", gad);
2353 if (shellMeFail("cd %s/%s; git add .gitattributes >>%s", dir, gar, gerr)) E("Failed to git add!");
2354 V("Committing initial git.");
2355 if (shellMeFail("cd %s/%s; git commit -qm \"Initial commit\" >>%s || echo \"ERROR - Could not commit!\" >>%s",
2356 dir, gar, glog, gerr)) E("Failed to git commit!");
2357 if (shellMeFail("cd %s/%s; git branch -m master Domme >>%s", dir, gar, gerr)) E("Failed to git branch -m master Domme!");
2358 }
2359 // Doing these each time, to catch those old ones that didn't have them.
2360 // Coz otherwise git commit starts a gc run IN THE BACKGROUD, which screws up trying to tarball it.
2361 if (shellMeFail("cd %s/%s; git config gc.autodetach false", dir, gar)) E("Failed to git config gc.autodetach!");
2362 // Don't want it running in the foreground either, coz it dumps on the console, we do a gc --quiet later anyway.
2363 if (shellMeFail("cd %s/%s; git config gc.auto 0", dir, gar)) E("Failed to git config gc.auto!");
2364
2365 // Git is such a pedantic bitch, let's just fucking ignore any errors it gives due to lack of anything to do.
2366 // Even worse the OpenSim devs breaking logout tracking gives git plenty of nothing to do. lol
2367
2368 // Loop through the .iar / .oar files in backups, ignoring zero sized files.
2369 struct dirtree *new = dirtree_add_node(0, scBackup, 0);
2370 int i;
2371
2372 ourARs->num = 0;
2373 ourARs->this = xmprintf("%s-", name);
2374 new->extra = (long) ourARs;
2375 dirtree_handle_callback(new, filterARs);
2376 qsort(ourARs->ARs, ourARs->num, sizeof(char *), qstrcmp);
2377 for (i = 0; i < ourARs->num; i++)
2378 {
2379 I("Adding %s to %s", ourARs->ARs[i], gtr);
2380 // Deal with deletions in the inventory / sim, easy method, which becomes a nop for files that stay in the git add below.
2381 if (shellMeFail("cd %s/%s; rm -fr * >>%s", dir, gar, gerr)) E("Failed to rm!");
2382 if (shellMeFail("cd %s/%s; ionice -c3 nice -n 19 tar -xzf \"%s/%s\" || echo \"ERROR - Could not unpack %s !\" >>%s",
2383 dir, gar, scBackup, ourARs->ARs[i], ourARs->ARs[i], gerr)) E("Failed to unpack %s!", ourARs->ARs[i]);
2384 if (!hasContents(gerr))
2385 {
2386 if (shellMeFail("cd %s/%s; git add * >>%s", dir, gar, glog)) E("Failed to git add!");
2387 // The \\* bit is to escape the \ from snprintf, which itself escapes the * from the shell.
2388 if (shellMeFail("cd %s/%s; git add */\\* >>%s", dir, gar, gerr)) E("Failed to git add!");
2389 // Gotta add this again, coz of the rm. Apparently rm removes dotfiles, but add doesn't!
2390 if (-1 == qfile_save(gab, gad, strlen(gad), false)) E("Faild to write %s file!", gab);
2391 if (shellMeFail("cd %s/%s; git add .gitattributes >>%s", dir, gar, gerr)) E("Failed to git add!");
2392
2393 // Magic needed to figure out if there's anything to commit.
2394 // After all the pain to get this to work, there's an ever changing timestamp in archive.xml that screws it up.
2395 // Like this system didn't have enough timestamps in it already. lol
2396 // TODO - I could sed out that timestamp, and put it back again based on the OAR file name when extracting.
2397 // IARs don't seem to have the timestamp.
2398 int j = shellMe("cd %s/%s; t=$(git status --porcelain) && [ -z \"${t}\" ]", dir, gar);
2399 if (!WIFEXITED(j)) E("git status failed!");
2400 else if (1 == WEXITSTATUS(j))
2401 {
2402 V("Committing changes from %s", ourARs->ARs[i]);
2403 // Note this commit message has to be just the file name, as the ungitAR script uses it.
2404 if (shellMeFail("cd %s/%s; git commit -a -qm \"%s\" >>%s || echo \"ERROR - Could not commit %s !\" >>%s ",
2405 dir, gar, ourARs->ARs[i], ourARs->ARs[i], glog, gerr)) E("Failed to git commit!");
2406 if (hasContents(gerr))
2407 {
2408 free(ourARs->ARs[i]);
2409 goto gitARend;
2410 }
2411 }
2412 else
2413 V("No changes to commit from %s.", ourARs->ARs[i]);
2414 }
2415 if (!hasContents(gerr))
2416 {
2417 if (shellMeFail("mv %s/%s %s", scBackup, ourARs->ARs[i], dir)) E("Failed to move %s!", ourARs->ARs[i]);
2418 }
2419 free(ourARs->ARs[i]);
2420 }
2421
2422 if (!hasContents(gerr))
2423 {
2424 I("Checking git repo %s", gtr);
2425 if (shellMeFail("cd %s/%s; git fsck --strict --lost-found --no-progress >>%s || echo \"ERROR - Problem with git fsck %s !\" >>%s ",
2426 dir, gar, glog, gtr, gerr)) E("Failed to git fsck!");
2427 if (shellMeFail("cd %s/%s; git gc --aggressive --prune=all --quiet >>%s || echo \"ERROR - Problem with git gc %s !\" >>%s ",
2428 dir, gar, glog, gtr, gerr)) E("Failed to git gc!");
2429 I("Compressing %s", gtr);
2430 if (shellMeFail("cd %s; XZ_OPT='-9e' ionice -c3 nice -n 19 tar -c --xz %s -f %s || echo 'ERROR - Could not pack gitAR!' >>%s",
2431 dir, gar, gtr, gerr)) E("Failed to git add!");
2432 }
2433gitARend:
2434 if (hasContents(gerr)) E("Failed to process the archives, look in %s for errors!", gerr);
2435 else
2436 {
2437 if (shellMeFail("rm -fr %s", dir)) E("Failed to rm!");
2438 }
2439 free(ourARs->ARs);
2440 free(ourARs->this);
2441 free(ourARs);
2442 free(gab);
2443 free(gerr);
2444 free(glog);
2445 free(dir);
2446 free(gtr);
2447 free(gar);
2448 free(name);
2449}
2274 2450
2275// Forward declare this. 2451// Forward declare this.
2276my_ulonglong dbCount(char *table, char *where); 2452my_ulonglong dbCount(char *table, char *where);
@@ -2349,7 +2525,7 @@ byTab has the short name as the key, simData as the value.
2349 if ('\0' == rSync[0]) 2525 if ('\0' == rSync[0])
2350 { 2526 {
2351 I("Running gitar on %s %s", sim, last); 2527 I("Running gitar on %s %s", sim, last);
2352 shellMeFail("%s/current/bin/sledjchisl gitar -m %s %s %s", scRoot, FLAG(v) ? "-v" : "", sim, last); 2528 gitar(simd, sim, count, window, panes, pane, true, member, last);
2353 } 2529 }
2354 sendTmuxCmd(ourSims->backup, toybuf); 2530 sendTmuxCmd(ourSims->backup, toybuf);
2355// if (0 == do) 2531// if (0 == do)
@@ -2382,7 +2558,7 @@ byTab has the short name as the key, simData as the value.
2382 if ('\0' == rSync[0]) 2558 if ('\0' == rSync[0])
2383 { 2559 {
2384 I("Running gitar on %s", simd->tab); 2560 I("Running gitar on %s", simd->tab);
2385 shellMeFail("%s/current/bin/sledjchisl gitar %s %s", scRoot, FLAG(v) ? "-v" : "", simd->tab); 2561 gitar(simd, sim, count, window, panes, pane, false, member, last);
2386 } 2562 }
2387 snprintf(toybuf, sizeof(toybuf), "save oar --all %s/%s-%s.oar", scBackup, simd->tab, date); 2563 snprintf(toybuf, sizeof(toybuf), "save oar --all %s/%s-%s.oar", scBackup, simd->tab, date);
2388 sendTmuxCmd(simd->paneID, toybuf); 2564 sendTmuxCmd(simd->paneID, toybuf);
@@ -2407,179 +2583,10 @@ byTab has the short name as the key, simData as the value.
2407 2583
2408 case GITAR : // "gitAR -m avatar name" "gitAR sim name" 2584 case GITAR : // "gitAR -m avatar name" "gitAR sim name"
2409 { 2585 {
2410 /* Work around OpenSims slow database corruption bug by using git to store all old backups. 2586 gitar(simd, sim, count, window, panes, pane, FLAG(m), member, last);
2411 Try to squeeze every last byte out of the tarballs. Seems to cut the total storage size down to one third the size of just the raw I/OAR files. 2587 break;
2412 Saves even more if there's been no changes. 2588 }
2413 On the other hand, these backup files will grow indefinately, the more changes, the faster it grows. I can live with that for more reliable backups that go back further.
2414 Tries to avoid loosing data if things go wrong. I think the main remaining problem would be running out of space, in which case you have bigger problems to deal with.
2415
2416 Strategy - unpack the last one, unpack and commit any old I/OARs, pack up the result, delete it's working directory, THEN run the save i/oar.
2417 Avoids having to sync with OpenSim finishing the current I/OAR, and as a bonus, an easy to deliver latest I/OAR for people that want it.
2418 */
2419 char *name = xstrdup(sim);
2420
2421 if (FLAG(m))
2422 {
2423 if (member)
2424 {
2425 free(name);
2426 name = xmprintf("%s_%s", sim, last);
2427 }
2428 else
2429 {
2430 free(name);
2431 return;
2432 }
2433 }
2434
2435 char type = FLAG(m) ? 'I' : 'O';
2436 char *gar = xmprintf("%s-git%cAR", name, type), *gtr = xmprintf("%s/%s.tar.xz", scBackup, gar);
2437 char *dir = xmprintf("%s/temp_%c_%s", scBackup, type, name);
2438 char *glog = xmprintf("%s/log.log", scBackup), *gerr = xmprintf("%s/errors", dir);
2439 // Make sure stuff that's already compressed doesn't get compressed by git.
2440 // Also tries to protect binaries from mangling.
2441 char *gab = xmprintf("%s/%s/.gitattributes", dir, gar), *gad =
2442 "*.dat -diff -text\n"
2443 "*.bvh -delta -diff -text\n"
2444 "*.j2c -delta -diff -text\n" // OpenSim uses all four JPEG 2000 extensions. In the same directories. lol
2445 "*.jp2 -delta -diff -text\n"
2446 "*.jpc -delta -diff -text\n"
2447 "*.jpg -delta -diff -text\n"
2448 "*.llmesh -delta -diff -text\n"
2449 "*.ogg -delta -diff -text\n"
2450 "*.png -delta -diff -text\n"
2451 "*.r32 -delta -diff -text\n"
2452 "*.tga -delta -diff -text\n";
2453 ARList *ourARs = xzalloc(sizeof(ARList));
2454
2455 if (qfile_exist(dir))
2456 {
2457 if (shellMeFail("echo 'Mess left over from last backup in %d, not gonna run!' >>%s", dir, gerr)) E("Cleaning up the mess!");
2458 if (shellMeFail("mv %s/*.%car %s 2>&1 >>%s", dir, tolower(type), scBackup, gerr)) E("Failed cleaning up the mess!");
2459 goto gitARend;
2460 }
2461
2462 // Either unpack the old gitAR, or create a new one.
2463 qfile_mkdir(dir, S_IRWXU | S_IRGRP | S_IXGRP, true);
2464 if (qfile_exist(gtr))
2465 {
2466 I("Unpacking %s", gtr);
2467 if (shellMeFail("cd %s; ionice -c3 nice -n 19 tar -xf %s >>%s", dir, gtr, gerr)) E("Failed to unpack %s!", gtr);
2468 char *t = xmprintf("%s/%s_git%cAR", dir, name, type);
2469 // Changed from _ to -, but deal with old archives.
2470 if (qfile_exist(t))
2471 {
2472 if (shellMeFail("mv %s %s/%s", t, dir, gar)) E("Failed to move %s!", t);
2473 }
2474 free(t);
2475 }
2476 else
2477 {
2478 // git will create gar for us.
2479 if (shellMeFail("cd %s; git init --quiet %s >>%s", dir, gar, gerr)) E("Failed to git init %s/%s!", dir, gar);
2480
2481 // Coz git insists.
2482 if (shellMeFail("cd %s/%s; git config user.email \"opensim@$(hostname -A | cut -d ' ' -f 1)\"", dir, gar)) E("Failed to git config user.email!");
2483 if (shellMeFail("cd %s/%s; git config user.name \"opensim\"", dir, gar)) E("Failed to git config user.name!");
2484
2485 // Coz git insists on having something comitted before it can change the branch name.
2486 if (-1 == qfile_save(gab, gad, strlen(gad), false)) E("Faild to write %s file!", gad);
2487 if (shellMeFail("cd %s/%s; git add .gitattributes >>%s", dir, gar, gerr)) E("Failed to git add!");
2488 V("Committing initial git.");
2489 if (shellMeFail("cd %s/%s; git commit -qm \"Initial commit\" >>%s || echo \"ERROR - Could not commit!\" >>%s",
2490 dir, gar, glog, gerr)) E("Failed to git commit!");
2491 if (shellMeFail("cd %s/%s; git branch -m master Domme >>%s", dir, gar, gerr)) E("Failed to git branch -m master Domme!");
2492 }
2493 // Doing these each time, to catch those old ones that didn't have them.
2494 // Coz otherwise git commit starts a gc run IN THE BACKGROUD, which screws up trying to tarball it.
2495 if (shellMeFail("cd %s/%s; git config gc.autodetach false", dir, gar)) E("Failed to git config gc.autodetach!");
2496 // Don't want it running in the foreground either, coz it dumps on the console, we do a gc --quiet later anyway.
2497 if (shellMeFail("cd %s/%s; git config gc.auto 0", dir, gar)) E("Failed to git config gc.auto!");
2498
2499 // Git is such a pedantic bitch, let's just fucking ignore any errors it gives due to lack of anything to do.
2500 // Even worse the OpenSim devs breaking logout tracking gives git plenty of nothing to do. lol
2501
2502 // Loop through the .iar / .oar files in backups, ignoring zero sized files.
2503 struct dirtree *new = dirtree_add_node(0, scBackup, 0);
2504 int i;
2505
2506 ourARs->num = 0;
2507 ourARs->this = xmprintf("%s-", name);
2508 new->extra = (long) ourARs;
2509 dirtree_handle_callback(new, filterARs);
2510 qsort(ourARs->ARs, ourARs->num, sizeof(char *), qstrcmp);
2511 for (i = 0; i < ourARs->num; i++)
2512 {
2513 I("Adding %s to %s", ourARs->ARs[i], gtr);
2514 // Deal with deletions in the inventory / sim, easy method, which becomes a nop for files that stay in the git add below.
2515 if (shellMeFail("cd %s/%s; rm -fr * >>%s", dir, gar, gerr)) E("Failed to rm!");
2516 if (shellMeFail("cd %s/%s; ionice -c3 nice -n 19 tar -xzf \"%s/%s\" || echo \"ERROR - Could not unpack %s !\" >>%s",
2517 dir, gar, scBackup, ourARs->ARs[i], ourARs->ARs[i], gerr)) E("Failed to unpack %s!", ourARs->ARs[i]);
2518 if (!hasContents(gerr))
2519 {
2520 if (shellMeFail("cd %s/%s; git add * >>%s", dir, gar, glog)) E("Failed to git add!");
2521 // The \\* bit is to escape the \ from snprintf, which itself escapes the * from the shell.
2522 if (shellMeFail("cd %s/%s; git add */\\* >>%s", dir, gar, gerr)) E("Failed to git add!");
2523 // Gotta add this again, coz of the rm. Apparently rm removes dotfiles, but add doesn't!
2524 if (-1 == qfile_save(gab, gad, strlen(gad), false)) E("Faild to write %s file!", gab);
2525 if (shellMeFail("cd %s/%s; git add .gitattributes >>%s", dir, gar, gerr)) E("Failed to git add!");
2526
2527 // Magic needed to figure out if there's anything to commit.
2528 // After all the pain to get this to work, there's an ever changing timestamp in archive.xml that screws it up.
2529 // Like this system didn't have enough timestamps in it already. lol
2530 // TODO - I could sed out that timestamp, and put it back again based on the OAR file name when extracting.
2531 // IARs don't seem to have the timestamp.
2532 int j = shellMe("cd %s/%s; t=$(git status --porcelain) && [ -z \"${t}\" ]", dir, gar);
2533 if (!WIFEXITED(j)) E("git status failed!");
2534 else if (1 == WEXITSTATUS(j))
2535 {
2536 V("Committing changes from %s", ourARs->ARs[i]);
2537 // Note this commit message has to be just the file name, as the ungitAR script uses it.
2538 if (shellMeFail("cd %s/%s; git commit -a -qm \"%s\" >>%s || echo \"ERROR - Could not commit %s !\" >>%s ",
2539 dir, gar, ourARs->ARs[i], ourARs->ARs[i], glog, gerr)) E("Failed to git commit!");
2540 if (hasContents(gerr))
2541 {
2542 free(ourARs->ARs[i]);
2543 goto gitARend;
2544 }
2545 }
2546 else
2547 V("No changes to commit from %s.", ourARs->ARs[i]);
2548 }
2549 if (!hasContents(gerr))
2550 {
2551 if (shellMeFail("mv %s/%s %s", scBackup, ourARs->ARs[i], dir)) E("Failed to move %s!", ourARs->ARs[i]);
2552 }
2553 free(ourARs->ARs[i]);
2554 }
2555 2589
2556 if (!hasContents(gerr))
2557 {
2558 I("Checking git repo %s", gtr);
2559 if (shellMeFail("cd %s/%s; git fsck --strict --lost-found --no-progress >>%s || echo \"ERROR - Problem with git fsck %s !\" >>%s ",
2560 dir, gar, glog, gtr, gerr)) E("Failed to git fsck!");
2561 if (shellMeFail("cd %s/%s; git gc --aggressive --prune=all --quiet >>%s || echo \"ERROR - Problem with git gc %s !\" >>%s ",
2562 dir, gar, glog, gtr, gerr)) E("Failed to git gc!");
2563 I("Compressing %s", gtr);
2564 if (shellMeFail("cd %s; XZ_OPT='-9e' ionice -c3 nice -n 19 tar -c --xz %s -f %s || echo 'ERROR - Could not pack gitAR!' >>%s",
2565 dir, gar, gtr, gerr)) E("Failed to git add!");
2566 }
2567gitARend:
2568 if (hasContents(gerr)) E("Failed to process the archives, look in %s for errors!", gerr);
2569 else
2570 {
2571 if (shellMeFail("rm -fr %s", dir)) E("Failed to rm!");
2572 }
2573 free(ourARs->ARs);
2574 free(ourARs->this);
2575 free(ourARs);
2576 free(gab);
2577 free(gerr);
2578 free(glog);
2579 free(dir);
2580 free(gtr);
2581 free(gar);
2582 free(name);
2583 break; 2590 break;
2584 } 2591 }
2585 2592