00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012 #include <vector>
00013
00014 #include <sbia/basis/config.h>
00015
00016 #include <stdlib.h>
00017 #include <string.h>
00018
00019 #if WINDOWS
00020 # include <direct.h>
00021 # include <windows.h>
00022 #else
00023 # include <unistd.h>
00024 # include <sys/stat.h>
00025 # include <dirent.h>
00026 #endif
00027 #if MACOS
00028 # include <mach-o/dyld.h>
00029 #endif
00030
00031 #include <sbia/basis/assert.h>
00032 #include <sbia/basis/except.h>
00033 #include <sbia/basis/path.h>
00034
00035
00036
00037 using namespace std;
00038
00039
00040 namespace sbia
00041 {
00042
00043 namespace basis
00044 {
00045
00046
00047
00048
00049
00050
00051 #if WINDOWS
00052 const char cPathSeparator = '\\';
00053 const string cPathSeparatorStr ("\\");
00054 #else
00055 const char cPathSeparator = '/';
00056 const string cPathSeparatorStr ("/");
00057 #endif
00058
00059
00060
00061
00062
00063
00064 bool is_valid_path(const string& path, bool strict)
00065 {
00066
00067 if (path.empty()) return false;
00068
00069 if (path[0] == ':') return false;
00070
00071 if (path.size() > 1 && path[1] == ':') {
00072
00073 if ((path[0] < 'a' || 'z' < path[0]) &&
00074 (path[0] < 'A' || 'Z' < path[0])) return false;
00075
00076 if (path.size() == 2) return false;
00077 if (path[2] != '/' && path[2] != '\\') return false;
00078 if (strict) {
00079 #if !WINDOWS
00080
00081
00082
00083 if (path[0] != 'C' && path[0] != 'c') return false;
00084 #endif
00085 }
00086 }
00087
00088 return true;
00089 }
00090
00091
00092 string clean_path(const string& path)
00093 {
00094 if (!is_valid_path(path, false)) {
00095 BASIS_THROW(invalid_argument, "Invalid path: ''");
00096 }
00097
00098 string cleaned_path;
00099
00100 string::const_iterator prev;
00101 string::const_iterator curr;
00102 string::const_iterator next;
00103
00104
00105 prev = path.begin();
00106 if (prev == path.end()) return "";
00107
00108 curr = prev + 1;
00109 next = ((curr == path.end()) ? curr : (curr + 1));
00110
00111 cleaned_path.reserve(path.size());
00112 cleaned_path.push_back(*prev);
00113 while (curr != path.end()) {
00114 if (*curr == '.' && (*prev == '/' || *prev == '\\')) {
00115 if (next == path.end()) {
00116
00117
00118 cleaned_path.erase(cleaned_path.size() - 1);
00119 }
00120 else if (*next != '/' && *next != '\\') {
00121 cleaned_path.push_back(*curr);
00122 }
00123 } else {
00124 cleaned_path.push_back(*curr);
00125 }
00126
00127 prev++;
00128 curr++;
00129 if (next != path.end()) next++;
00130 }
00131
00132
00133 string tmp;
00134 cleaned_path.swap(tmp);
00135
00136 prev = tmp.begin();
00137 curr = prev + 1;
00138
00139 cleaned_path.reserve(tmp.size());
00140 cleaned_path.push_back(*prev);
00141 while (curr != tmp.end()) {
00142 if ((*curr != '/' && *curr != '\\') || (*prev != '/' && *prev != '\\')) {
00143 cleaned_path.push_back(*curr);
00144 }
00145
00146 prev++;
00147 curr++;
00148 }
00149
00150
00151 size_t skip = 0;
00152
00153
00154
00155 while (skip + 3 <= cleaned_path.size()) {
00156 string sub = cleaned_path.substr(skip, 3);
00157 if (sub != "../" && sub != "..\\") break;
00158 skip += 3;
00159 }
00160 if (skip > 0) skip -= 1;
00161
00162 string ref = "/..";
00163 size_t pos = skip;
00164
00165
00166 for (;;) {
00167 pos = cleaned_path.find(ref, pos);
00168 if (pos == string::npos) {
00169 if (ref == "\\..") break;
00170
00171 ref = "\\..";
00172 pos = skip;
00173 pos = cleaned_path.find(ref, pos);
00174 if (pos == string::npos) break;
00175 }
00176 if (pos + 3 == cleaned_path.size()
00177 || cleaned_path[pos + 3] == '/'
00178 || cleaned_path[pos + 3] == '\\') {
00179 if (pos == 0) {
00180 if (cleaned_path.size() == 3) cleaned_path.erase(1, 2);
00181 else cleaned_path.erase(0, 3);
00182 pos = skip;
00183 } else {
00184 size_t start = cleaned_path.find_last_of("/\\", pos - 1);
00185 if (start != string::npos) {
00186 cleaned_path.erase(start, pos - start + 3);
00187 pos = start + 1;
00188 } else if (cleaned_path[0] == '/' || cleaned_path[0] == '\\') {
00189 cleaned_path.erase(skip, pos + 3);
00190 pos = skip;
00191 } else {
00192 pos += 3;
00193 }
00194 }
00195 } else {
00196 pos += 3;
00197 }
00198 }
00199
00200 return cleaned_path;
00201 }
00202
00203
00204 string to_unix_path(const string& path, bool drive)
00205 {
00206 if (!is_valid_path(path)) {
00207 BASIS_THROW(invalid_argument, "Invalid path: '" << path << "'");
00208 }
00209
00210 string unix_path;
00211 unix_path.reserve(path.size());
00212
00213 string::const_iterator in = path.begin();
00214
00215
00216 if (path.size() > 1 && path[1] == ':') {
00217 if (drive) {
00218 if ('a' <= path[0] && path[0] <= 'z') unix_path += 'A' + (path[0] - 'a');
00219 else unix_path += path[0];
00220 unix_path += ":/";
00221 }
00222 in += 2;
00223 }
00224
00225
00226 while (in != path.end()) {
00227 if (*in == '\\') unix_path.push_back('/');
00228 else unix_path.push_back(*in);
00229 in++;
00230 }
00231
00232 return clean_path(unix_path);
00233 }
00234
00235
00236 string to_windows_path(const string& path)
00237 {
00238 if (!is_valid_path(path, false)) {
00239 BASIS_THROW(invalid_argument, "Invalid path: '" << path << "'");
00240 }
00241
00242 string windows_path(path.size(), '\0');
00243
00244
00245 string::const_iterator in = path.begin();
00246 string::iterator out = windows_path.begin();
00247
00248 while (in != path.end()) {
00249 if (*in == '/') *out = '\\';
00250 else *out = *in;
00251 in++;
00252 out++;
00253 }
00254
00255
00256 if (windows_path[0] == '\\') {
00257 windows_path.reserve(windows_path.size() + 2);
00258 windows_path.insert (0, "C:");
00259 }
00260
00261 return clean_path(windows_path);
00262 }
00263
00264
00265 string to_native_path(const string& path)
00266 {
00267 #if WINDOWS
00268 return to_windows_path(path);
00269 #else
00270 return to_unix_path(path);
00271 #endif
00272 }
00273
00274
00275
00276
00277
00278
00279 string get_working_directory()
00280 {
00281 string wd;
00282 #if WINDOWS
00283 char* buffer = _getcwd(NULL, 0);
00284 #else
00285 char* buffer = getcwd(NULL, 0);
00286 #endif
00287 if (buffer) {
00288 wd = buffer;
00289 free(buffer);
00290 }
00291 if (!wd.empty()) wd = to_unix_path(wd, true);
00292 return wd;
00293 }
00294
00295
00296
00297
00298
00299
00300 void split_path(const string&path, string* root, string* dir, string* fname,
00301 string* ext, const set<string>* exts)
00302 {
00303
00304 if (root) *root = get_file_root(path);
00305
00306
00307 else if (!is_valid_path(path)) {
00308 BASIS_THROW(invalid_argument, "Invalid path: '" << path << "'");
00309 }
00310
00311 string unix_path = to_unix_path(path);
00312
00313 size_t last = unix_path.find_last_of('/');
00314
00315 if (dir) {
00316 if (last == string::npos) {
00317 *dir = "";
00318 } else {
00319 size_t start = 0;
00320
00321 if (unix_path[0] == '/') {
00322 start = 1;
00323 } else if (unix_path.size() > 1 &&
00324 unix_path[0] == '.' &&
00325 unix_path[1] == '/') {
00326 start = 2;
00327 }
00328
00329 *dir = unix_path.substr(start, last - start + 1);
00330 }
00331 }
00332
00333 if (fname || ext) {
00334 string name;
00335
00336 if (last == string::npos) {
00337 name = unix_path;
00338 } else {
00339 name = unix_path.substr(last + 1);
00340 }
00341
00342 size_t pos = string::npos;
00343
00344
00345 if (exts && exts->size() > 0) {
00346 for (set<string>::const_iterator i = exts->begin(); i != exts->end(); ++i) {
00347 size_t start = name.size() - i->size();
00348 if (start < pos && name.compare(start, i->size(), *i) == 0) {
00349 pos = start;
00350 }
00351 }
00352 }
00353
00354 if (pos == string::npos) {
00355 pos = name.find_last_of('.');
00356 }
00357
00358 if (pos == string::npos) {
00359 if (fname) {
00360 *fname = name;
00361 }
00362 if (ext) {
00363 *ext = "";
00364 }
00365 } else {
00366 if (fname) {
00367 *fname = name.substr(0, pos);
00368 }
00369 if (ext) {
00370 *ext = name.substr(pos);
00371 }
00372 }
00373 }
00374 }
00375
00376
00377 string get_file_root(const string& path)
00378 {
00379 if (!is_valid_path(path)) {
00380 BASIS_THROW(invalid_argument, "Invalid path: '" << path << "'");
00381 }
00382
00383
00384 if (path[0] == '/' || path[0] == '\\') {
00385 #if WINDOWS
00386 return "C:/";
00387 #else
00388 return "/";
00389 #endif
00390 }
00391
00392 if (path.size() > 1 && path[1] == ':') {
00393 char letter = path[0];
00394 if ('a' <= letter && letter <= 'z') {
00395 letter = 'A' + (letter - 'a');
00396 }
00397 #if WINDOWS
00398 string root; root += letter; root += ":/";
00399 return root;
00400 #else
00401 return "/";
00402 #endif
00403 }
00404
00405 return "./";
00406 }
00407
00408
00409 string get_file_directory(const string& path)
00410 {
00411 string root;
00412 string dir;
00413 split_path(path, &root, &dir, NULL, NULL);
00414 if (root == "./") {
00415 if (dir.empty()) return "./";
00416 else return dir;
00417 } else {
00418 return root + dir.substr(0, dir.size() - 1);
00419 }
00420 }
00421
00422
00423 string get_file_name(const string& path)
00424 {
00425 string fname;
00426 string ext;
00427 split_path(path, NULL, NULL, &fname, &ext);
00428 return fname + ext;
00429 }
00430
00431
00432 string get_file_name_without_extension(const string& path, const set<string>* exts)
00433 {
00434 string fname;
00435 split_path(path, NULL, NULL, &fname, NULL, exts);
00436 return fname;
00437 }
00438
00439
00440 string get_file_name_extension(const string& path, const set<string>* exts)
00441 {
00442 string ext;
00443 split_path(path, NULL, NULL, NULL, &ext, exts);
00444 return ext;
00445 }
00446
00447
00448 bool has_extension(const string& path, const set<string>* exts)
00449 {
00450 string ext = get_file_name_extension(path, exts);
00451 return exts ? exts->find(ext) != exts->end() : !ext.empty();
00452 }
00453
00454
00455
00456
00457
00458
00459 bool is_absolute(const string& path)
00460 {
00461 return get_file_root(path) != "./";
00462 }
00463
00464
00465 bool is_relative(const string& path)
00466 {
00467 return get_file_root(path) == "./";
00468 }
00469
00470
00471 string to_absolute_path(const string& path)
00472 {
00473 return to_absolute_path(get_working_directory(), path);
00474 }
00475
00476
00477 string to_absolute_path(const string& base, const string& path)
00478 {
00479 string abs_path(path);
00480
00481 if (is_relative(abs_path)) {
00482 string abs_base(base);
00483 if (is_relative(abs_base)) {
00484 abs_base.insert(0, get_working_directory() + '/');
00485 }
00486 abs_path.insert(0, abs_base + '/');
00487 }
00488
00489 abs_path = to_unix_path(abs_path, true);
00490
00491 #if WINDOWS
00492 if (abs_path[0] == '/') abs_path.insert(0, "C:");
00493 #endif
00494 return abs_path;
00495 }
00496
00497
00498 string to_relative_path(const string& path)
00499 {
00500 string unix_path = to_unix_path(path, true);
00501 if (is_relative(unix_path)) return clean_path(unix_path);
00502 return to_relative_path(get_working_directory(), unix_path);
00503 }
00504
00505
00506 string to_relative_path(const string& base, const string& path)
00507 {
00508 string unix_path = to_unix_path(path, true);
00509
00510 if (is_relative(unix_path)) return clean_path(path);
00511
00512 string abs_base = to_unix_path(base, true);
00513 if (is_relative(abs_base)) {
00514 abs_base.insert(0, get_working_directory() + '/');
00515 }
00516
00517
00518 if (get_file_root(abs_base) != get_file_root(unix_path)) return "";
00519
00520 string::const_iterator b = abs_base .begin();
00521 string::const_iterator p = unix_path.begin();
00522 size_t pos = 0;
00523 size_t i = 0;
00524 while (b != abs_base.end() && p != unix_path.end() && *b == *p) {
00525 if (*p == '/') pos = i;
00526 b++; p++; i++;
00527 }
00528
00529
00530
00531 if ((b != abs_base .end() && (*b == '/' || *b == '\\')) ||
00532 (p != unix_path.end() && (*p == '/' || *p == '\\'))) pos = i;
00533
00534 if (b == abs_base .end() && p != unix_path.end() && *p == '/') p++;
00535 if (p == unix_path.end() && b != abs_base .end() && *b == '/') b++;
00536
00537
00538
00539
00540
00541
00542
00543
00544
00545
00546
00547 if (b == abs_base.end() && p == unix_path.end()) return ".";
00548
00549
00550
00551
00552 string rel_path;
00553
00554
00555 if (b != abs_base.end() && abs_base[abs_base.size() - 1] != '/') {
00556
00557
00558 size_t pos = b - abs_base.begin();
00559 abs_base += '/';
00560 b = abs_base.begin() + pos;
00561 }
00562 while (b != abs_base.end()) {
00563 if (*b == '/') rel_path += "../";
00564 b++;
00565 }
00566 if (pos + 1 < unix_path.size()) rel_path += unix_path.substr(pos + 1);
00567
00568 if (rel_path[rel_path.size() - 1] == '/') {
00569 rel_path.erase (rel_path.size() - 1);
00570 }
00571 return rel_path;
00572 }
00573
00574
00575 string join_paths(const string& base, const string& path)
00576 {
00577 if (is_absolute(path)) return clean_path(path);
00578 else return clean_path(base + '/' + path);
00579 }
00580
00581
00582
00583
00584
00585
00586 bool is_file(const std::string path)
00587 {
00588 #if WINDOWS
00589 const DWORD info = ::GetFileAttributes(path.c_str());
00590 return (FILE_ATTRIBUTE_DIRECTORY & info) == 0;
00591 #else
00592 struct stat info;
00593 if (stat(path.c_str(), &info) != 0) return false;
00594 return S_ISREG(info.st_mode);
00595 #endif
00596 return false;
00597 }
00598
00599
00600 bool is_dir(const std::string path)
00601 {
00602 #if WINDOWS
00603 const DWORD info = ::GetFileAttributes(path.c_str());
00604 return (FILE_ATTRIBUTE_DIRECTORY & info) != 0;
00605 #else
00606 struct stat info;
00607 if (stat(path.c_str(), &info) != 0) return false;
00608 return S_ISDIR(info.st_mode);
00609 #endif
00610 return false;
00611 }
00612
00613
00614 bool exists(const std::string path)
00615 {
00616 #if WINDOWS
00617 const DWORD info = ::GetFileAttributes(path.c_str());
00618 return info != INVALID_FILE_ATTRIBUTES;
00619 #else
00620 struct stat info;
00621 if (stat(path.c_str(), &info) == 0) return true;
00622 #endif
00623 return false;
00624 }
00625
00626
00627 bool is_symlink(const string& path)
00628 {
00629 if (!is_valid_path(path)) {
00630 BASIS_THROW(invalid_argument, "Invalid path: '" << path << "'");
00631 }
00632
00633 #if WINDOWS
00634 return false;
00635 #else
00636 struct stat info;
00637 if (lstat(path.c_str(), &info) != 0) return false;
00638 return S_ISLNK(info.st_mode);
00639 #endif
00640 }
00641
00642
00643
00644
00645
00646
00647 bool make_directory(const string& path, bool parent)
00648 {
00649 if (path.empty() || is_file(path)) return false;
00650 vector<string> dirs;
00651 string dir(path);
00652 if (parent) {
00653 while (!dir.empty() && !exists(dir)) {
00654 dirs.push_back(dir);
00655 dir = get_file_directory(dir);
00656 }
00657 } else if (!exists(dir)) {
00658 dirs.push_back(dir);
00659 }
00660 for (vector<string>::reverse_iterator it = dirs.rbegin(); it != dirs.rend(); ++it) {
00661 #if WINDOWS
00662 if (CreateDirectory(it->c_str(), NULL) == FALSE) return false;
00663 #else
00664 if (mkdir(it->c_str(), 0755) != 0) return false;
00665 #endif
00666 }
00667 return true;
00668 }
00669
00670
00671 bool remove_directory(const string& path, bool recursive)
00672 {
00673
00674 if (recursive && !clear_directory(path)) return false;
00675
00676 #if WINDOWS
00677 return (::SetFileAttributes(path.c_str(), FILE_ATTRIBUTE_NORMAL) == TRUE) &&
00678 (::RemoveDirectory(path.c_str()) == TRUE);
00679 #else
00680 return rmdir(path.c_str()) == 0;
00681 #endif
00682 }
00683
00684
00685 bool clear_directory(const string& path)
00686 {
00687 bool ok = true;
00688 string subpath;
00689
00690 #if WINDOWS
00691 WIN32_FIND_DATA info;
00692 HANDLE hFile = ::FindFirstFile(join_paths(path, "*.*").c_str(), &info);
00693 if (hFile != INVALID_HANDLE_VALUE) {
00694 do {
00695
00696 if (strncmp(info.cFileName, ".", 2) == 0 || strncmp(info.cFileName, "..", 3) == 0) {
00697 continue;
00698 }
00699
00700 subpath = join_paths(path, info.cFileName);
00701 if(info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
00702 if (!remove_directory(subpath, true)) ok = false;
00703 } else {
00704 if (::SetFileAttributes(subpath.c_str(), FILE_ATTRIBUTE_NORMAL) == FALSE ||
00705 ::DeleteFile(subpath.c_str()) == FALSE) ok = false;
00706 }
00707 } while (::FindNextFile(hFile, &info) == TRUE);
00708 ::FindClose(hFile);
00709 }
00710 #else
00711 struct dirent *p = NULL;
00712 DIR *d = opendir(path.c_str());
00713 if (d != NULL) {
00714 while ((p = readdir(d)) != NULL) {
00715
00716 if (strncmp(p->d_name, ".", 2) == 0 || strncmp(p->d_name, "..", 3) == 0) {
00717 continue;
00718 }
00719
00720 subpath = join_paths(path, p->d_name);
00721 if (is_dir(subpath)) {
00722 if (!remove_directory(subpath, true)) ok = false;
00723 } else {
00724 if (unlink(subpath.c_str()) != 0) ok = false;
00725 }
00726 }
00727 closedir(d);
00728 }
00729 #endif
00730 return ok;
00731 }
00732
00733
00734
00735
00736
00737
00738 bool read_symlink(const string& link, string& value)
00739 {
00740 if (!is_valid_path(link)) {
00741 BASIS_THROW(invalid_argument, "Invalid path: '" << link << "'");
00742 }
00743
00744 bool ok = true;
00745 #if WINDOWS
00746 value = link;
00747 #else
00748 char* buffer = NULL;
00749 char* newbuf = NULL;
00750 size_t buflen = 256;
00751
00752 for (;;) {
00753 newbuf = reinterpret_cast<char*>(realloc(buffer, buflen * sizeof(char)));
00754 if (!newbuf) {
00755 ok = false;
00756 break;
00757 }
00758 buffer = newbuf;
00759
00760 int n = readlink(link.c_str(), buffer, buflen);
00761
00762 if (n < 0) {
00763 ok = false;
00764 break;
00765 } else if (static_cast<size_t>(n) < buflen) {
00766 buffer[n] = '\0';
00767 value = buffer;
00768 break;
00769 }
00770 buflen += 256;
00771 }
00772
00773 free(buffer);
00774 #endif
00775 return ok;
00776 }
00777
00778
00779 string get_real_path(const string& path)
00780 {
00781 string curr_path = to_absolute_path(path);
00782 #if UNIX
00783
00784 stringstream ss(curr_path);
00785 curr_path.clear();
00786 string fname;
00787 string prev_path;
00788 string next_path;
00789 char slash;
00790 ss >> slash;
00791 assert(slash == '/');
00792 while (getline(ss, fname, '/')) {
00793
00794 curr_path += '/';
00795 curr_path += fname;
00796
00797 if (is_symlink(curr_path)) {
00798
00799 for (unsigned int i = 0; i < 100; i++) {
00800 if (read_symlink(curr_path, next_path)) {
00801 curr_path = to_absolute_path(prev_path, next_path);
00802 if (!is_symlink(next_path)) break;
00803 } else {
00804
00805
00806 break;
00807 }
00808 }
00809
00810
00811
00812 if (is_symlink(next_path)) {
00813 return to_absolute_path(path);
00814 }
00815 }
00816
00817 prev_path = curr_path;
00818 }
00819 #endif
00820 return curr_path;
00821 }
00822
00823
00824
00825
00826
00827
00828 string get_executable_path()
00829 {
00830 string path;
00831 #if LINUX
00832 path = get_real_path("/proc/self/exe");
00833 #elif WINDOWS
00834 LPTSTR buffer = NULL;
00835 LPTSTR newbuf = NULL;
00836 DWORD buflen = 256;
00837 DWORD retval = 0;
00838
00839 for (;;) {
00840 newbuf = static_cast<LPTSTR>(realloc(buffer, buflen * sizeof(TCHAR)));
00841 if (!newbuf) break;
00842 buffer = newbuf;
00843 retval = GetModuleFileName(NULL, buffer, buflen);
00844 if (retval == 0 || retval < buflen) break;
00845 buflen += 256;
00846 retval = 0;
00847 }
00848
00849 if (retval > 0) {
00850 # ifdef UNICODE
00851 int n = WideCharToMultiByte(CP_UTF8, 0, buffer, -1, NULL, 0, NULL, NULL);
00852 char* mbpath = static_cast<char*>(malloc(n));
00853 if (mbpath) {
00854 WideCharToMultiByte(CP_UTF8, 0, buffer, -1, mbpath, n, NULL, NULL);
00855 path = mbpath;
00856 free(mbpath);
00857 }
00858 # else
00859 path = buffer;
00860 # endif
00861 }
00862
00863 free (buffer);
00864 #elif MACOS
00865 char* buffer = NULL;
00866 char* newbuf = NULL;
00867 uint32_t buflen = 256;
00868
00869 buffer = reinterpret_cast<char*>(malloc(buflen * sizeof(char)));
00870 if (buffer) {
00871 if (_NSGetExecutablePath(buffer, &buflen) == 0) {
00872 path = buffer;
00873 } else {
00874 newbuf = reinterpret_cast<char*>(realloc(buffer, buflen * sizeof(char)));
00875 if (newbuf) {
00876 buffer = newbuf;
00877 if (_NSGetExecutablePath(buffer, &buflen) == 0) {
00878 path = buffer;
00879 }
00880 }
00881 }
00882 }
00883
00884 free(buffer);
00885 #else
00886
00887 #endif
00888 return clean_path(path);
00889 }
00890
00891
00892 string get_executable_directory()
00893 {
00894 string path = get_executable_path();
00895 return path.empty() ? "" : get_file_directory(path);
00896 }
00897
00898
00899 string get_executable_name()
00900 {
00901 string name = get_executable_path();
00902 if (name.empty()) return "";
00903
00904 #if WINDOWS
00905 string ext = get_file_name_extension(name);
00906 if (ext == ".exe" || ext == ".com") {
00907 name = get_file_name_without_extension(name);
00908 } else {
00909 name = get_file_name(name);
00910 }
00911 #else
00912 name = get_file_name(name);
00913 #endif
00914
00915 return name;
00916 }
00917
00918
00919 }
00920
00921 }