00001
00002
00003
00004
00005 #include "ruby/ruby.h"
00006 #include "ruby/util.h"
00007 #include "internal.h"
00008 #include "dln.h"
00009 #include "eval_intern.h"
00010
00011 VALUE ruby_dln_librefs;
00012
00013 #define IS_RBEXT(e) (strcmp((e), ".rb") == 0)
00014 #define IS_SOEXT(e) (strcmp((e), ".so") == 0 || strcmp((e), ".o") == 0)
00015 #ifdef DLEXT2
00016 #define IS_DLEXT(e) (strcmp((e), DLEXT) == 0 || strcmp((e), DLEXT2) == 0)
00017 #else
00018 #define IS_DLEXT(e) (strcmp((e), DLEXT) == 0)
00019 #endif
00020
00021
00022 static const char *const loadable_ext[] = {
00023 ".rb", DLEXT,
00024 #ifdef DLEXT2
00025 DLEXT2,
00026 #endif
00027 0
00028 };
00029
00030 VALUE
00031 rb_get_load_path(void)
00032 {
00033 VALUE load_path = GET_VM()->load_path;
00034 return load_path;
00035 }
00036
00037 VALUE
00038 rb_get_expanded_load_path(void)
00039 {
00040 VALUE load_path = rb_get_load_path();
00041 VALUE ary;
00042 long i;
00043
00044 ary = rb_ary_new2(RARRAY_LEN(load_path));
00045 for (i = 0; i < RARRAY_LEN(load_path); ++i) {
00046 VALUE path = rb_file_expand_path(RARRAY_PTR(load_path)[i], Qnil);
00047 rb_str_freeze(path);
00048 rb_ary_push(ary, path);
00049 }
00050 rb_obj_freeze(ary);
00051 return ary;
00052 }
00053
00054 static VALUE
00055 load_path_getter(ID id, rb_vm_t *vm)
00056 {
00057 return vm->load_path;
00058 }
00059
00060 static VALUE
00061 get_loaded_features(void)
00062 {
00063 return GET_VM()->loaded_features;
00064 }
00065
00066 static st_table *
00067 get_loading_table(void)
00068 {
00069 return GET_VM()->loading_table;
00070 }
00071
00072 static VALUE
00073 loaded_feature_path(const char *name, long vlen, const char *feature, long len,
00074 int type, VALUE load_path)
00075 {
00076 long i;
00077 long plen;
00078 const char *e;
00079
00080 if(vlen < len) return 0;
00081 if (!strncmp(name+(vlen-len),feature,len)){
00082 plen = vlen - len - 1;
00083 } else {
00084 for (e = name + vlen; name != e && *e != '.' && *e != '/'; --e);
00085 if (*e!='.' ||
00086 e-name < len ||
00087 strncmp(e-len,feature,len) )
00088 return 0;
00089 plen = e - name - len - 1;
00090 }
00091 for (i = 0; i < RARRAY_LEN(load_path); ++i) {
00092 VALUE p = RARRAY_PTR(load_path)[i];
00093 const char *s = StringValuePtr(p);
00094 long n = RSTRING_LEN(p);
00095
00096 if (n != plen ) continue;
00097 if (n && (strncmp(name, s, n) || name[n] != '/')) continue;
00098 switch (type) {
00099 case 's':
00100 if (IS_DLEXT(&name[n+len+1])) return p;
00101 break;
00102 case 'r':
00103 if (IS_RBEXT(&name[n+len+1])) return p;
00104 break;
00105 default:
00106 return p;
00107 }
00108 }
00109 return 0;
00110 }
00111
00112 struct loaded_feature_searching {
00113 const char *name;
00114 long len;
00115 int type;
00116 VALUE load_path;
00117 const char *result;
00118 };
00119
00120 static int
00121 loaded_feature_path_i(st_data_t v, st_data_t b, st_data_t f)
00122 {
00123 const char *s = (const char *)v;
00124 struct loaded_feature_searching *fp = (struct loaded_feature_searching *)f;
00125 VALUE p = loaded_feature_path(s, strlen(s), fp->name, fp->len,
00126 fp->type, fp->load_path);
00127 if (!p) return ST_CONTINUE;
00128 fp->result = s;
00129 return ST_STOP;
00130 }
00131
00132 static int
00133 rb_feature_p(const char *feature, const char *ext, int rb, int expanded, const char **fn)
00134 {
00135 VALUE v, features, p, load_path = 0;
00136 const char *f, *e;
00137 long i, len, elen, n;
00138 st_table *loading_tbl;
00139 st_data_t data;
00140 int type;
00141
00142 if (fn) *fn = 0;
00143 if (ext) {
00144 elen = strlen(ext);
00145 len = strlen(feature) - elen;
00146 type = rb ? 'r' : 's';
00147 }
00148 else {
00149 len = strlen(feature);
00150 elen = 0;
00151 type = 0;
00152 }
00153 features = get_loaded_features();
00154 for (i = 0; i < RARRAY_LEN(features); ++i) {
00155 v = RARRAY_PTR(features)[i];
00156 f = StringValuePtr(v);
00157 if ((n = RSTRING_LEN(v)) < len) continue;
00158 if (strncmp(f, feature, len) != 0) {
00159 if (expanded) continue;
00160 if (!load_path) load_path = rb_get_expanded_load_path();
00161 if (!(p = loaded_feature_path(f, n, feature, len, type, load_path)))
00162 continue;
00163 expanded = 1;
00164 f += RSTRING_LEN(p) + 1;
00165 }
00166 if (!*(e = f + len)) {
00167 if (ext) continue;
00168 return 'u';
00169 }
00170 if (*e != '.') continue;
00171 if ((!rb || !ext) && (IS_SOEXT(e) || IS_DLEXT(e))) {
00172 return 's';
00173 }
00174 if ((rb || !ext) && (IS_RBEXT(e))) {
00175 return 'r';
00176 }
00177 }
00178 loading_tbl = get_loading_table();
00179 if (loading_tbl) {
00180 f = 0;
00181 if (!expanded) {
00182 struct loaded_feature_searching fs;
00183 fs.name = feature;
00184 fs.len = len;
00185 fs.type = type;
00186 fs.load_path = load_path ? load_path : rb_get_load_path();
00187 fs.result = 0;
00188 st_foreach(loading_tbl, loaded_feature_path_i, (st_data_t)&fs);
00189 if ((f = fs.result) != 0) {
00190 if (fn) *fn = f;
00191 goto loading;
00192 }
00193 }
00194 if (st_get_key(loading_tbl, (st_data_t)feature, &data)) {
00195 if (fn) *fn = (const char*)data;
00196 loading:
00197 if (!ext) return 'u';
00198 return !IS_RBEXT(ext) ? 's' : 'r';
00199 }
00200 else {
00201 VALUE bufstr;
00202 char *buf;
00203
00204 if (ext && *ext) return 0;
00205 bufstr = rb_str_tmp_new(len + DLEXT_MAXLEN);
00206 buf = RSTRING_PTR(bufstr);
00207 MEMCPY(buf, feature, char, len);
00208 for (i = 0; (e = loadable_ext[i]) != 0; i++) {
00209 strlcpy(buf + len, e, DLEXT_MAXLEN + 1);
00210 if (st_get_key(loading_tbl, (st_data_t)buf, &data)) {
00211 rb_str_resize(bufstr, 0);
00212 if (fn) *fn = (const char*)data;
00213 return i ? 's' : 'r';
00214 }
00215 }
00216 rb_str_resize(bufstr, 0);
00217 }
00218 }
00219 return 0;
00220 }
00221
00222 int
00223 rb_provided(const char *feature)
00224 {
00225 return rb_feature_provided(feature, 0);
00226 }
00227
00228 int
00229 rb_feature_provided(const char *feature, const char **loading)
00230 {
00231 const char *ext = strrchr(feature, '.');
00232 volatile VALUE fullpath = 0;
00233
00234 if (*feature == '.' &&
00235 (feature[1] == '/' || strncmp(feature+1, "./", 2) == 0)) {
00236 fullpath = rb_file_expand_path(rb_str_new2(feature), Qnil);
00237 feature = RSTRING_PTR(fullpath);
00238 }
00239 if (ext && !strchr(ext, '/')) {
00240 if (IS_RBEXT(ext)) {
00241 if (rb_feature_p(feature, ext, TRUE, FALSE, loading)) return TRUE;
00242 return FALSE;
00243 }
00244 else if (IS_SOEXT(ext) || IS_DLEXT(ext)) {
00245 if (rb_feature_p(feature, ext, FALSE, FALSE, loading)) return TRUE;
00246 return FALSE;
00247 }
00248 }
00249 if (rb_feature_p(feature, 0, TRUE, FALSE, loading))
00250 return TRUE;
00251 return FALSE;
00252 }
00253
00254 static void
00255 rb_provide_feature(VALUE feature)
00256 {
00257 if (OBJ_FROZEN(get_loaded_features())) {
00258 rb_raise(rb_eRuntimeError,
00259 "$LOADED_FEATURES is frozen; cannot append feature");
00260 }
00261 rb_ary_push(get_loaded_features(), feature);
00262 }
00263
00264 void
00265 rb_provide(const char *feature)
00266 {
00267 rb_provide_feature(rb_usascii_str_new2(feature));
00268 }
00269
00270 NORETURN(static void load_failed(VALUE));
00271
00272 static void
00273 rb_load_internal(VALUE fname, int wrap)
00274 {
00275 int state;
00276 rb_thread_t *th = GET_THREAD();
00277 volatile VALUE wrapper = th->top_wrapper;
00278 volatile VALUE self = th->top_self;
00279 volatile int loaded = FALSE;
00280 volatile int mild_compile_error;
00281 #ifndef __GNUC__
00282 rb_thread_t *volatile th0 = th;
00283 #endif
00284
00285 th->errinfo = Qnil;
00286
00287 if (!wrap) {
00288 rb_secure(4);
00289 th->top_wrapper = 0;
00290 }
00291 else {
00292
00293 th->top_self = rb_obj_clone(rb_vm_top_self());
00294 th->top_wrapper = rb_module_new();
00295 rb_extend_object(th->top_self, th->top_wrapper);
00296 }
00297
00298 mild_compile_error = th->mild_compile_error;
00299 PUSH_TAG();
00300 state = EXEC_TAG();
00301 if (state == 0) {
00302 NODE *node;
00303 VALUE iseq;
00304
00305 th->mild_compile_error++;
00306 node = (NODE *)rb_load_file(RSTRING_PTR(fname));
00307 loaded = TRUE;
00308 iseq = rb_iseq_new_top(node, rb_str_new2("<top (required)>"), fname, rb_realpath_internal(Qnil, fname, 1), Qfalse);
00309 th->mild_compile_error--;
00310 rb_iseq_eval(iseq);
00311 }
00312 POP_TAG();
00313
00314 #ifndef __GNUC__
00315 th = th0;
00316 fname = RB_GC_GUARD(fname);
00317 #endif
00318 th->mild_compile_error = mild_compile_error;
00319 th->top_self = self;
00320 th->top_wrapper = wrapper;
00321
00322 if (!loaded) {
00323 rb_exc_raise(GET_THREAD()->errinfo);
00324 }
00325 if (state) {
00326 rb_vm_jump_tag_but_local_jump(state, Qundef);
00327 }
00328
00329 if (!NIL_P(GET_THREAD()->errinfo)) {
00330
00331 rb_exc_raise(th->errinfo);
00332 }
00333 }
00334
00335 void
00336 rb_load(VALUE fname, int wrap)
00337 {
00338 VALUE tmp = rb_find_file(FilePathValue(fname));
00339 if (!tmp) load_failed(fname);
00340 rb_load_internal(tmp, wrap);
00341 }
00342
00343 void
00344 rb_load_protect(VALUE fname, int wrap, int *state)
00345 {
00346 int status;
00347
00348 PUSH_TAG();
00349 if ((status = EXEC_TAG()) == 0) {
00350 rb_load(fname, wrap);
00351 }
00352 POP_TAG();
00353 if (state)
00354 *state = status;
00355 }
00356
00357
00358
00359
00360
00361
00362
00363
00364
00365
00366
00367
00368
00369
00370
00371 static VALUE
00372 rb_f_load(int argc, VALUE *argv)
00373 {
00374 VALUE fname, wrap, path;
00375
00376 rb_scan_args(argc, argv, "11", &fname, &wrap);
00377 path = rb_find_file(FilePathValue(fname));
00378 if (!path) {
00379 if (!rb_file_load_ok(RSTRING_PTR(fname)))
00380 load_failed(fname);
00381 path = fname;
00382 }
00383 rb_load_internal(path, RTEST(wrap));
00384 return Qtrue;
00385 }
00386
00387 static char *
00388 load_lock(const char *ftptr)
00389 {
00390 st_data_t data;
00391 st_table *loading_tbl = get_loading_table();
00392
00393 if (!loading_tbl || !st_lookup(loading_tbl, (st_data_t)ftptr, &data)) {
00394
00395 if (!loading_tbl) {
00396 GET_VM()->loading_table = loading_tbl = st_init_strtable();
00397 }
00398
00399 ftptr = ruby_strdup(ftptr);
00400 data = (st_data_t)rb_barrier_new();
00401 st_insert(loading_tbl, (st_data_t)ftptr, data);
00402 return (char *)ftptr;
00403 }
00404 if (RTEST(ruby_verbose)) {
00405 rb_warning("loading in progress, circular require considered harmful - %s", ftptr);
00406 rb_backtrace();
00407 }
00408 return RTEST(rb_barrier_wait((VALUE)data)) ? (char *)ftptr : 0;
00409 }
00410
00411 static void
00412 load_unlock(const char *ftptr, int done)
00413 {
00414 if (ftptr) {
00415 st_data_t key = (st_data_t)ftptr;
00416 st_data_t data;
00417 st_table *loading_tbl = get_loading_table();
00418
00419 if (st_delete(loading_tbl, &key, &data)) {
00420 VALUE barrier = (VALUE)data;
00421 xfree((char *)key);
00422 if (done)
00423 rb_barrier_destroy(barrier);
00424 else
00425 rb_barrier_release(barrier);
00426 }
00427 }
00428 }
00429
00430
00431
00432
00433
00434
00435
00436
00437
00438
00439
00440
00441
00442
00443
00444
00445
00446
00447
00448
00449
00450
00451
00452
00453
00454
00455
00456
00457
00458
00459
00460
00461
00462 VALUE
00463 rb_f_require(VALUE obj, VALUE fname)
00464 {
00465 return rb_require_safe(fname, rb_safe_level());
00466 }
00467
00468
00469
00470
00471
00472
00473
00474
00475
00476 VALUE
00477 rb_f_require_relative(VALUE obj, VALUE fname)
00478 {
00479 VALUE base = rb_current_realfilepath();
00480 if (NIL_P(base)) {
00481 rb_raise(rb_eLoadError, "cannot infer basepath");
00482 }
00483 base = rb_file_dirname(base);
00484 return rb_require_safe(rb_file_absolute_path(fname, base), rb_safe_level());
00485 }
00486
00487 static int
00488 search_required(VALUE fname, volatile VALUE *path, int safe_level)
00489 {
00490 VALUE tmp;
00491 char *ext, *ftptr;
00492 int type, ft = 0;
00493 const char *loading;
00494
00495 *path = 0;
00496 ext = strrchr(ftptr = RSTRING_PTR(fname), '.');
00497 if (ext && !strchr(ext, '/')) {
00498 if (IS_RBEXT(ext)) {
00499 if (rb_feature_p(ftptr, ext, TRUE, FALSE, &loading)) {
00500 if (loading) *path = rb_str_new2(loading);
00501 return 'r';
00502 }
00503 if ((tmp = rb_find_file_safe(fname, safe_level)) != 0) {
00504 ext = strrchr(ftptr = RSTRING_PTR(tmp), '.');
00505 if (!rb_feature_p(ftptr, ext, TRUE, TRUE, &loading) || loading)
00506 *path = tmp;
00507 return 'r';
00508 }
00509 return 0;
00510 }
00511 else if (IS_SOEXT(ext)) {
00512 if (rb_feature_p(ftptr, ext, FALSE, FALSE, &loading)) {
00513 if (loading) *path = rb_str_new2(loading);
00514 return 's';
00515 }
00516 tmp = rb_str_new(RSTRING_PTR(fname), ext - RSTRING_PTR(fname));
00517 #ifdef DLEXT2
00518 OBJ_FREEZE(tmp);
00519 if (rb_find_file_ext_safe(&tmp, loadable_ext + 1, safe_level)) {
00520 ext = strrchr(ftptr = RSTRING_PTR(tmp), '.');
00521 if (!rb_feature_p(ftptr, ext, FALSE, TRUE, &loading) || loading)
00522 *path = tmp;
00523 return 's';
00524 }
00525 #else
00526 rb_str_cat2(tmp, DLEXT);
00527 OBJ_FREEZE(tmp);
00528 if ((tmp = rb_find_file_safe(tmp, safe_level)) != 0) {
00529 ext = strrchr(ftptr = RSTRING_PTR(tmp), '.');
00530 if (!rb_feature_p(ftptr, ext, FALSE, TRUE, &loading) || loading)
00531 *path = tmp;
00532 return 's';
00533 }
00534 #endif
00535 }
00536 else if (IS_DLEXT(ext)) {
00537 if (rb_feature_p(ftptr, ext, FALSE, FALSE, &loading)) {
00538 if (loading) *path = rb_str_new2(loading);
00539 return 's';
00540 }
00541 if ((tmp = rb_find_file_safe(fname, safe_level)) != 0) {
00542 ext = strrchr(ftptr = RSTRING_PTR(tmp), '.');
00543 if (!rb_feature_p(ftptr, ext, FALSE, TRUE, &loading) || loading)
00544 *path = tmp;
00545 return 's';
00546 }
00547 }
00548 }
00549 else if ((ft = rb_feature_p(ftptr, 0, FALSE, FALSE, &loading)) == 'r') {
00550 if (loading) *path = rb_str_new2(loading);
00551 return 'r';
00552 }
00553 tmp = fname;
00554 type = rb_find_file_ext_safe(&tmp, loadable_ext, safe_level);
00555 switch (type) {
00556 case 0:
00557 if (ft)
00558 break;
00559 ftptr = RSTRING_PTR(tmp);
00560 return rb_feature_p(ftptr, 0, FALSE, TRUE, 0);
00561
00562 default:
00563 if (ft)
00564 break;
00565 case 1:
00566 ext = strrchr(ftptr = RSTRING_PTR(tmp), '.');
00567 if (rb_feature_p(ftptr, ext, !--type, TRUE, &loading) && !loading)
00568 break;
00569 *path = tmp;
00570 }
00571 return type ? 's' : 'r';
00572 }
00573
00574 static void
00575 load_failed(VALUE fname)
00576 {
00577 VALUE mesg = rb_str_buf_new_cstr("cannot load such file -- ");
00578 rb_str_append(mesg, fname);
00579 rb_exc_raise(rb_exc_new3(rb_eLoadError, mesg));
00580 }
00581
00582 static VALUE
00583 load_ext(VALUE path)
00584 {
00585 SCOPE_SET(NOEX_PUBLIC);
00586 return (VALUE)dln_load(RSTRING_PTR(path));
00587 }
00588
00589 VALUE
00590 rb_require_safe(VALUE fname, int safe)
00591 {
00592 volatile VALUE result = Qnil;
00593 rb_thread_t *th = GET_THREAD();
00594 volatile VALUE errinfo = th->errinfo;
00595 int state;
00596 struct {
00597 int safe;
00598 } volatile saved;
00599 char *volatile ftptr = 0;
00600
00601 PUSH_TAG();
00602 saved.safe = rb_safe_level();
00603 if ((state = EXEC_TAG()) == 0) {
00604 VALUE path;
00605 long handle;
00606 int found;
00607
00608 rb_set_safe_level_force(safe);
00609 FilePathValue(fname);
00610 rb_set_safe_level_force(0);
00611 found = search_required(fname, &path, safe);
00612 if (found) {
00613 if (!path || !(ftptr = load_lock(RSTRING_PTR(path)))) {
00614 result = Qfalse;
00615 }
00616 else {
00617 switch (found) {
00618 case 'r':
00619 rb_load_internal(path, 0);
00620 break;
00621
00622 case 's':
00623 handle = (long)rb_vm_call_cfunc(rb_vm_top_self(), load_ext,
00624 path, 0, path);
00625 rb_ary_push(ruby_dln_librefs, LONG2NUM(handle));
00626 break;
00627 }
00628 rb_provide_feature(path);
00629 result = Qtrue;
00630 }
00631 }
00632 }
00633 POP_TAG();
00634 load_unlock(ftptr, !state);
00635
00636 rb_set_safe_level_force(saved.safe);
00637 if (state) {
00638 JUMP_TAG(state);
00639 }
00640
00641 if (NIL_P(result)) {
00642 load_failed(fname);
00643 }
00644
00645 th->errinfo = errinfo;
00646
00647 return result;
00648 }
00649
00650 VALUE
00651 rb_require(const char *fname)
00652 {
00653 VALUE fn = rb_str_new2(fname);
00654 OBJ_FREEZE(fn);
00655 return rb_require_safe(fn, rb_safe_level());
00656 }
00657
00658 static VALUE
00659 init_ext_call(VALUE arg)
00660 {
00661 SCOPE_SET(NOEX_PUBLIC);
00662 (*(void (*)(void))arg)();
00663 return Qnil;
00664 }
00665
00666 RUBY_FUNC_EXPORTED void
00667 ruby_init_ext(const char *name, void (*init)(void))
00668 {
00669 if (load_lock(name)) {
00670 rb_vm_call_cfunc(rb_vm_top_self(), init_ext_call, (VALUE)init,
00671 0, rb_str_new2(name));
00672 rb_provide(name);
00673 load_unlock(name, 1);
00674 }
00675 }
00676
00677
00678
00679
00680
00681
00682
00683
00684
00685
00686
00687
00688
00689
00690
00691 static VALUE
00692 rb_mod_autoload(VALUE mod, VALUE sym, VALUE file)
00693 {
00694 ID id = rb_to_id(sym);
00695
00696 FilePathValue(file);
00697 rb_autoload(mod, id, RSTRING_PTR(file));
00698 return Qnil;
00699 }
00700
00701
00702
00703
00704
00705
00706
00707
00708
00709
00710
00711
00712
00713
00714 static VALUE
00715 rb_mod_autoload_p(VALUE mod, VALUE sym)
00716 {
00717 return rb_autoload_p(mod, rb_to_id(sym));
00718 }
00719
00720
00721
00722
00723
00724
00725
00726
00727
00728
00729
00730
00731 static VALUE
00732 rb_f_autoload(VALUE obj, VALUE sym, VALUE file)
00733 {
00734 VALUE klass = rb_class_real(rb_vm_cbase());
00735 if (NIL_P(klass)) {
00736 rb_raise(rb_eTypeError, "Can not set autoload on singleton class");
00737 }
00738 return rb_mod_autoload(klass, sym, file);
00739 }
00740
00741
00742
00743
00744
00745
00746
00747
00748
00749
00750
00751
00752 static VALUE
00753 rb_f_autoload_p(VALUE obj, VALUE sym)
00754 {
00755
00756 VALUE klass = rb_vm_cbase();
00757 if (NIL_P(klass)) {
00758 return Qnil;
00759 }
00760 return rb_mod_autoload_p(klass, sym);
00761 }
00762
00763 void
00764 Init_load()
00765 {
00766 #undef rb_intern
00767 #define rb_intern(str) rb_intern2((str), strlen(str))
00768 rb_vm_t *vm = GET_VM();
00769 static const char var_load_path[] = "$:";
00770 ID id_load_path = rb_intern2(var_load_path, sizeof(var_load_path)-1);
00771
00772 rb_define_hooked_variable(var_load_path, (VALUE*)vm, load_path_getter, rb_gvar_readonly_setter);
00773 rb_alias_variable(rb_intern("$-I"), id_load_path);
00774 rb_alias_variable(rb_intern("$LOAD_PATH"), id_load_path);
00775 vm->load_path = rb_ary_new();
00776
00777 rb_define_virtual_variable("$\"", get_loaded_features, 0);
00778 rb_define_virtual_variable("$LOADED_FEATURES", get_loaded_features, 0);
00779 vm->loaded_features = rb_ary_new();
00780
00781 rb_define_global_function("load", rb_f_load, -1);
00782 rb_define_global_function("require", rb_f_require, 1);
00783 rb_define_global_function("require_relative", rb_f_require_relative, 1);
00784 rb_define_method(rb_cModule, "autoload", rb_mod_autoload, 2);
00785 rb_define_method(rb_cModule, "autoload?", rb_mod_autoload_p, 1);
00786 rb_define_global_function("autoload", rb_f_autoload, 2);
00787 rb_define_global_function("autoload?", rb_f_autoload_p, 1);
00788
00789 ruby_dln_librefs = rb_ary_new();
00790 rb_gc_register_mark_object(ruby_dln_librefs);
00791 }
00792