To: vim_dev@googlegroups.com Subject: Patch 9.0.1583 Fcc: outbox From: Bram Moolenaar Mime-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ------------ Patch 9.0.1583 Problem: Get E304 when using 'cryptmethod' "xchacha20v2". (Steve Mynott) Solution: Add 4th crypt method to block zero ID check. Avoid syncing a swap file before reading the file. (closes #12433) Files: src/memfile.c, src/memline.c, src/crypt.c, src/proto/crypt.pro, src/structs.h, src/buffer.c, src/fileio.c, src/testdir/test_crypt.vim *** ../vim-9.0.1582/src/memfile.c 2023-04-22 21:14:21.585140551 +0100 --- src/memfile.c 2023-05-27 16:28:42.056761913 +0100 *************** *** 150,156 **** mfp->mf_free_first = NULL; // free list is empty mfp->mf_used_first = NULL; // used list is empty mfp->mf_used_last = NULL; ! mfp->mf_dirty = FALSE; mfp->mf_used_count = 0; mf_hash_init(&mfp->mf_hash); mf_hash_init(&mfp->mf_trans); --- 150,156 ---- mfp->mf_free_first = NULL; // free list is empty mfp->mf_used_first = NULL; // used list is empty mfp->mf_used_last = NULL; ! mfp->mf_dirty = MF_DIRTY_NO; mfp->mf_used_count = 0; mf_hash_init(&mfp->mf_hash); mf_hash_init(&mfp->mf_trans); *************** *** 224,230 **** if (mfp->mf_fd < 0) return FAIL; ! mfp->mf_dirty = TRUE; return OK; } --- 224,230 ---- if (mfp->mf_fd < 0) return FAIL; ! mfp->mf_dirty = MF_DIRTY_YES; return OK; } *************** *** 386,392 **** } } hp->bh_flags = BH_LOCKED | BH_DIRTY; // new block is always dirty ! mfp->mf_dirty = TRUE; hp->bh_page_count = page_count; mf_ins_used(mfp, hp); mf_ins_hash(mfp, hp); --- 386,392 ---- } } hp->bh_flags = BH_LOCKED | BH_DIRTY; // new block is always dirty ! mfp->mf_dirty = MF_DIRTY_YES; hp->bh_page_count = page_count; mf_ins_used(mfp, hp); mf_ins_hash(mfp, hp); *************** *** 483,489 **** if (dirty) { flags |= BH_DIRTY; ! mfp->mf_dirty = TRUE; } hp->bh_flags = flags; if (infile) --- 483,490 ---- if (dirty) { flags |= BH_DIRTY; ! if (mfp->mf_dirty != MF_DIRTY_YES_NOSYNC) ! mfp->mf_dirty = MF_DIRTY_YES; } hp->bh_flags = flags; if (infile) *************** *** 528,536 **** bhdr_T *hp; int got_int_save = got_int; ! if (mfp->mf_fd < 0) // there is no file, nothing to do { ! mfp->mf_dirty = FALSE; return FAIL; } --- 529,538 ---- bhdr_T *hp; int got_int_save = got_int; ! if (mfp->mf_fd < 0) { ! // there is no file, nothing to do ! mfp->mf_dirty = MF_DIRTY_NO; return FAIL; } *************** *** 576,582 **** * In case of an error this flag is also set, to avoid trying all the time. */ if (hp == NULL || status == FAIL) ! mfp->mf_dirty = FALSE; if ((flags & MFS_FLUSH) && *p_sws != NUL) { --- 578,584 ---- * In case of an error this flag is also set, to avoid trying all the time. */ if (hp == NULL || status == FAIL) ! mfp->mf_dirty = MF_DIRTY_NO; if ((flags & MFS_FLUSH) && *p_sws != NUL) { *************** *** 675,681 **** for (hp = mfp->mf_used_last; hp != NULL; hp = hp->bh_prev) if (hp->bh_bnum > 0) hp->bh_flags |= BH_DIRTY; ! mfp->mf_dirty = TRUE; } /* --- 677,683 ---- for (hp = mfp->mf_used_last; hp != NULL; hp = hp->bh_prev) if (hp->bh_bnum > 0) hp->bh_flags |= BH_DIRTY; ! mfp->mf_dirty = MF_DIRTY_YES; } /* *** ../vim-9.0.1582/src/memline.c 2023-04-27 21:13:09.184215933 +0100 --- src/memline.c 2023-05-27 16:29:35.588799270 +0100 *************** *** 64,70 **** #define BLOCK0_ID1_C0 'c' // block 0 id 1 'cm' 0 #define BLOCK0_ID1_C1 'C' // block 0 id 1 'cm' 1 #define BLOCK0_ID1_C2 'd' // block 0 id 1 'cm' 2 ! // BLOCK0_ID1_C3 and BLOCK0_ID1_C4 are for libsodium enctyption. However, for // these the swapfile is disabled, thus they will not be used. Added for // consistency anyway. #define BLOCK0_ID1_C3 'S' // block 0 id 1 'cm' 3 --- 64,70 ---- #define BLOCK0_ID1_C0 'c' // block 0 id 1 'cm' 0 #define BLOCK0_ID1_C1 'C' // block 0 id 1 'cm' 1 #define BLOCK0_ID1_C2 'd' // block 0 id 1 'cm' 2 ! // BLOCK0_ID1_C3 and BLOCK0_ID1_C4 are for libsodium encryption. However, for // these the swapfile is disabled, thus they will not be used. Added for // consistency anyway. #define BLOCK0_ID1_C3 'S' // block 0 id 1 'cm' 3 *************** *** 807,812 **** --- 807,814 ---- continue; if (mf_open_file(mfp, fname) == OK) // consumes fname! { + // don't sync yet in ml_sync_all() + mfp->mf_dirty = MF_DIRTY_YES_NOSYNC; #if defined(MSWIN) /* * set full pathname for swap file now, because a ":!cd dir" may *************** *** 939,945 **** && b0p->b0_id[1] != BLOCK0_ID1_C0 && b0p->b0_id[1] != BLOCK0_ID1_C1 && b0p->b0_id[1] != BLOCK0_ID1_C2 ! && b0p->b0_id[1] != BLOCK0_ID1_C3) ) return FAIL; return OK; --- 941,948 ---- && b0p->b0_id[1] != BLOCK0_ID1_C0 && b0p->b0_id[1] != BLOCK0_ID1_C1 && b0p->b0_id[1] != BLOCK0_ID1_C2 ! && b0p->b0_id[1] != BLOCK0_ID1_C3 ! && b0p->b0_id[1] != BLOCK0_ID1_C4) ) return FAIL; return OK; *************** *** 2513,2519 **** need_check_timestamps = TRUE; // give message later } } ! if (buf->b_ml.ml_mfp->mf_dirty) { (void)mf_sync(buf->b_ml.ml_mfp, (check_char ? MFS_STOP : 0) | (bufIsChanged(buf) ? MFS_FLUSH : 0)); --- 2516,2522 ---- need_check_timestamps = TRUE; // give message later } } ! if (buf->b_ml.ml_mfp->mf_dirty == MF_DIRTY_YES) { (void)mf_sync(buf->b_ml.ml_mfp, (check_char ? MFS_STOP : 0) | (bufIsChanged(buf) ? MFS_FLUSH : 0)); *** ../vim-9.0.1582/src/crypt.c 2023-05-20 16:39:03.337433572 +0100 --- src/crypt.c 2023-05-27 17:48:30.240642842 +0100 *************** *** 525,531 **** if (arg.cat_seed_len > 0) arg.cat_seed = header + CRYPT_MAGIC_LEN + arg.cat_salt_len; if (arg.cat_add_len > 0) ! arg.cat_add = header + CRYPT_MAGIC_LEN + arg.cat_salt_len + arg.cat_seed_len; return crypt_create(method_nr, key, &arg); } --- 525,532 ---- if (arg.cat_seed_len > 0) arg.cat_seed = header + CRYPT_MAGIC_LEN + arg.cat_salt_len; if (arg.cat_add_len > 0) ! arg.cat_add = header + CRYPT_MAGIC_LEN ! + arg.cat_salt_len + arg.cat_seed_len; return crypt_create(method_nr, key, &arg); } *************** *** 603,609 **** if (arg.cat_seed_len > 0) arg.cat_seed = *header + CRYPT_MAGIC_LEN + arg.cat_salt_len; if (arg.cat_add_len > 0) ! arg.cat_add = *header + CRYPT_MAGIC_LEN + arg.cat_salt_len + arg.cat_seed_len; // TODO: Should this be crypt method specific? (Probably not worth // it). sha2_seed is pretty bad for large amounts of entropy, so make --- 604,611 ---- if (arg.cat_seed_len > 0) arg.cat_seed = *header + CRYPT_MAGIC_LEN + arg.cat_salt_len; if (arg.cat_add_len > 0) ! arg.cat_add = *header + CRYPT_MAGIC_LEN ! + arg.cat_salt_len + arg.cat_seed_len; // TODO: Should this be crypt method specific? (Probably not worth // it). sha2_seed is pretty bad for large amounts of entropy, so make *************** *** 795,804 **** } } ! #ifdef FEAT_SODIUM ! static void crypt_check_swapfile_curbuf(void) { int method = crypt_get_method_nr(curbuf); if (crypt_method_is_sodium(method)) { --- 797,810 ---- } } ! /* ! * If the crypt method for "curbuf" does not support encrypting the swap file ! * then disable the swap file. ! */ ! void crypt_check_swapfile_curbuf(void) { + #ifdef FEAT_SODIUM int method = crypt_get_method_nr(curbuf); if (crypt_method_is_sodium(method)) { *************** *** 809,816 **** msg_scroll = TRUE; msg(_("Note: Encryption of swapfile not supported, disabling swap file")); } - } #endif void crypt_check_current_method(void) --- 815,822 ---- msg_scroll = TRUE; msg(_("Note: Encryption of swapfile not supported, disabling swap file")); } #endif + } void crypt_check_current_method(void) *************** *** 863,871 **** set_option_value_give_err((char_u *)"key", 0L, p1, OPT_LOCAL); crypt_free_key(p1); p1 = curbuf->b_p_key; - #ifdef FEAT_SODIUM crypt_check_swapfile_curbuf(); - #endif } break; } --- 869,875 ---- *************** *** 959,965 **** sodium_free(sd_state); return FAIL; } ! if (state->method_nr == CRYPT_M_SOD2) { memcpy(arg->cat_add, &opslimit, sizeof(opslimit)); arg->cat_add += sizeof(opslimit); --- 963,970 ---- sodium_free(sd_state); return FAIL; } ! // "cat_add" should not be NULL, check anyway for safety ! if (state->method_nr == CRYPT_M_SOD2 && arg->cat_add != NULL) { memcpy(arg->cat_add, &opslimit, sizeof(opslimit)); arg->cat_add += sizeof(opslimit); *** ../vim-9.0.1582/src/proto/crypt.pro 2023-04-23 17:50:14.853935966 +0100 --- src/proto/crypt.pro 2023-05-27 17:41:05.316792383 +0100 *************** *** 22,27 **** --- 22,28 ---- void crypt_decode_inplace(cryptstate_T *state, char_u *buf, size_t len, int last); void crypt_free_key(char_u *key); void crypt_check_method(int method); + void crypt_check_swapfile_curbuf(void); void crypt_check_current_method(void); char_u *crypt_get_key(int store, int twice); void crypt_append_msg(buf_T *buf); *** ../vim-9.0.1582/src/structs.h 2023-05-06 22:21:07.247211940 +0100 --- src/structs.h 2023-05-27 17:22:02.661070039 +0100 *************** *** 691,696 **** --- 691,702 ---- int cmod_did_esilent; // incremented when emsg_silent is } cmdmod_T; + typedef enum { + MF_DIRTY_NO = 0, // no dirty blocks + MF_DIRTY_YES, // there are dirty blocks + MF_DIRTY_YES_NOSYNC, // there are dirty blocks, do not sync yet + } mfdirty_T; + #define MF_SEED_LEN 8 struct memfile *************** *** 712,718 **** blocknr_T mf_neg_count; // number of negative blocks numbers blocknr_T mf_infile_count; // number of pages in the file unsigned mf_page_size; // number of bytes in a page ! int mf_dirty; // TRUE if there are dirty blocks #ifdef FEAT_CRYPT buf_T *mf_buffer; // buffer this memfile is for char_u mf_seed[MF_SEED_LEN]; // seed for encryption --- 718,724 ---- blocknr_T mf_neg_count; // number of negative blocks numbers blocknr_T mf_infile_count; // number of pages in the file unsigned mf_page_size; // number of bytes in a page ! mfdirty_T mf_dirty; #ifdef FEAT_CRYPT buf_T *mf_buffer; // buffer this memfile is for char_u mf_seed[MF_SEED_LEN]; // seed for encryption *** ../vim-9.0.1582/src/buffer.c 2023-05-23 18:00:53.947320728 +0100 --- src/buffer.c 2023-05-27 17:15:39.001590554 +0100 *************** *** 218,223 **** --- 218,227 ---- return FAIL; } + // Do not sync this buffer yet, may first want to read the file. + if (curbuf->b_ml.ml_mfp != NULL) + curbuf->b_ml.ml_mfp->mf_dirty = MF_DIRTY_YES_NOSYNC; + // The autocommands in readfile() may change the buffer, but only AFTER // reading the file. set_bufref(&old_curbuf, curbuf); *************** *** 298,303 **** --- 302,312 ---- retval = read_buffer(TRUE, eap, flags); } + // Can now sync this buffer in ml_sync_all(). + if (curbuf->b_ml.ml_mfp != NULL + && curbuf->b_ml.ml_mfp->mf_dirty == MF_DIRTY_YES_NOSYNC) + curbuf->b_ml.ml_mfp->mf_dirty = MF_DIRTY_YES; + // if first time loading this buffer, init b_chartab[] if (curbuf->b_flags & BF_NEVERLOADED) { *** ../vim-9.0.1582/src/fileio.c 2023-04-23 17:50:14.853935966 +0100 --- src/fileio.c 2023-05-27 17:41:25.320790220 +0100 *************** *** 125,130 **** --- 125,131 ---- exarg_T *eap, // can be NULL! int flags) { + int retval = FAIL; // jump to "theend" instead of returning int fd = 0; int newfile = (flags & READ_NEW); int check_readonly; *************** *** 239,245 **** && !(flags & READ_DUMMY)) { if (set_rw_fname(fname, sfname) == FAIL) ! return FAIL; } // Remember the initial values of curbuf, curbuf->b_ffname and --- 240,246 ---- && !(flags & READ_DUMMY)) { if (set_rw_fname(fname, sfname) == FAIL) ! goto theend; } // Remember the initial values of curbuf, curbuf->b_ffname and *************** *** 289,323 **** if (apply_autocmds_exarg(EVENT_BUFREADCMD, NULL, sfname, FALSE, curbuf, eap)) { ! int status = OK; #ifdef FEAT_EVAL if (aborting()) ! status = FAIL; #endif // The BufReadCmd code usually uses ":read" to get the text and // perhaps ":file" to change the buffer name. But we should // consider this to work like ":edit", thus reset the // BF_NOTEDITED flag. Then ":write" will work to overwrite the // same file. ! if (status == OK) curbuf->b_flags &= ~BF_NOTEDITED; ! return status; } } else if (apply_autocmds_exarg(EVENT_FILEREADCMD, sfname, sfname, FALSE, NULL, eap)) #ifdef FEAT_EVAL ! return aborting() ? FAIL : OK; #else ! return OK; #endif curbuf->b_op_start = orig_start; if (flags & READ_NOFILE) // Return NOTDONE instead of FAIL so that BufEnter can be triggered // and other operations don't fail. ! return NOTDONE; } if ((shortmess(SHM_OVER) || curbuf->b_help) && p_verbose == 0) --- 290,330 ---- if (apply_autocmds_exarg(EVENT_BUFREADCMD, NULL, sfname, FALSE, curbuf, eap)) { ! retval = OK; #ifdef FEAT_EVAL if (aborting()) ! retval = FAIL; #endif // The BufReadCmd code usually uses ":read" to get the text and // perhaps ":file" to change the buffer name. But we should // consider this to work like ":edit", thus reset the // BF_NOTEDITED flag. Then ":write" will work to overwrite the // same file. ! if (retval == OK) curbuf->b_flags &= ~BF_NOTEDITED; ! goto theend; } } else if (apply_autocmds_exarg(EVENT_FILEREADCMD, sfname, sfname, FALSE, NULL, eap)) + { #ifdef FEAT_EVAL ! retval = aborting() ? FAIL : OK; #else ! retval = OK; #endif + goto theend; + } curbuf->b_op_start = orig_start; if (flags & READ_NOFILE) + { // Return NOTDONE instead of FAIL so that BufEnter can be triggered // and other operations don't fail. ! retval = NOTDONE; ! goto theend; ! } } if ((shortmess(SHM_OVER) || curbuf->b_help) && p_verbose == 0) *************** *** 335,341 **** filemess(curbuf, fname, (char_u *)_("Illegal file name"), 0); msg_end(); msg_scroll = msg_save; ! return FAIL; } // If the name ends in a path separator, we can't open it. Check here, --- 342,348 ---- filemess(curbuf, fname, (char_u *)_("Illegal file name"), 0); msg_end(); msg_scroll = msg_save; ! goto theend; } // If the name ends in a path separator, we can't open it. Check here, *************** *** 346,352 **** filemess(curbuf, fname, (char_u *)_(msg_is_a_directory), 0); msg_end(); msg_scroll = msg_save; ! return NOTDONE; } } --- 353,360 ---- filemess(curbuf, fname, (char_u *)_(msg_is_a_directory), 0); msg_end(); msg_scroll = msg_save; ! retval = NOTDONE; ! goto theend; } } *************** *** 367,374 **** # endif ) { - int retval = FAIL; - if (S_ISDIR(perm)) { filemess(curbuf, fname, (char_u *)_(msg_is_a_directory), 0); --- 375,380 ---- *************** *** 378,384 **** filemess(curbuf, fname, (char_u *)_("is not a file"), 0); msg_end(); msg_scroll = msg_save; ! return retval; } #endif #if defined(MSWIN) --- 384,390 ---- filemess(curbuf, fname, (char_u *)_("is not a file"), 0); msg_end(); msg_scroll = msg_save; ! goto theend; } #endif #if defined(MSWIN) *************** *** 391,397 **** filemess(curbuf, fname, (char_u *)_("is a device (disabled with 'opendevice' option)"), 0); msg_end(); msg_scroll = msg_save; ! return FAIL; } #endif } --- 397,403 ---- filemess(curbuf, fname, (char_u *)_("is a device (disabled with 'opendevice' option)"), 0); msg_end(); msg_scroll = msg_save; ! goto theend; } #endif } *************** *** 534,540 **** && (old_b_fname != curbuf->b_fname))) { emsg(_(e_autocommands_changed_buffer_or_buffer_name)); ! return FAIL; } } if (dir_of_file_exists(fname)) --- 540,546 ---- && (old_b_fname != curbuf->b_fname))) { emsg(_(e_autocommands_changed_buffer_or_buffer_name)); ! goto theend; } } if (dir_of_file_exists(fname)) *************** *** 557,566 **** save_file_ff(curbuf); #if defined(FEAT_EVAL) ! if (aborting()) // autocmds may abort script processing ! return FAIL; #endif ! return OK; // a new file is not an error } else { --- 563,572 ---- save_file_ff(curbuf); #if defined(FEAT_EVAL) ! if (!aborting()) // autocmds may abort script processing #endif ! retval = OK; // a new file is not an error ! goto theend; } else { *************** *** 576,582 **** } } ! return FAIL; } /* --- 582,588 ---- } } ! goto theend; } /* *************** *** 614,620 **** emsg(_(e_autocommands_changed_buffer_or_buffer_name)); if (!read_buffer) close(fd); ! return FAIL; } #ifdef UNIX // Set swap file protection bits after creating it. --- 620,626 ---- emsg(_(e_autocommands_changed_buffer_or_buffer_name)); if (!read_buffer) close(fd); ! goto theend; } #ifdef UNIX // Set swap file protection bits after creating it. *************** *** 654,660 **** { if (!read_buffer && !read_stdin) close(fd); ! return FAIL; } ++no_wait_return; // don't wait for return yet --- 660,666 ---- { if (!read_buffer && !read_stdin) close(fd); ! goto theend; } ++no_wait_return; // don't wait for return yet *************** *** 715,721 **** --no_wait_return; msg_scroll = msg_save; curbuf->b_p_ro = TRUE; // must use "w!" now ! return FAIL; } #endif /* --- 721,727 ---- --no_wait_return; msg_scroll = msg_save; curbuf->b_p_ro = TRUE; // must use "w!" now ! goto theend; } #endif /* *************** *** 737,743 **** else emsg(_(e_readpre_autocommands_must_not_change_current_buffer)); curbuf->b_p_ro = TRUE; // must use "w!" now ! return FAIL; } } --- 743,749 ---- else emsg(_(e_readpre_autocommands_must_not_change_current_buffer)); curbuf->b_p_ro = TRUE; // must use "w!" now ! goto theend; } } *************** *** 2461,2467 **** #ifdef FEAT_VIMINFO check_marks_read(); #endif ! return OK; // an interrupt isn't really an error } if (!filtering && !(flags & READ_DUMMY)) --- 2467,2474 ---- #ifdef FEAT_VIMINFO check_marks_read(); #endif ! retval = OK; // an interrupt isn't really an error ! goto theend; } if (!filtering && !(flags & READ_DUMMY)) *************** *** 2696,2708 **** msg_scroll = m; # ifdef FEAT_EVAL if (aborting()) // autocmds may abort script processing ! return FAIL; # endif } ! if (recoverymode && error) ! return FAIL; ! return OK; } #if defined(OPEN_CHR_FILES) || defined(PROTO) --- 2703,2722 ---- msg_scroll = m; # ifdef FEAT_EVAL if (aborting()) // autocmds may abort script processing ! goto theend; # endif } ! if (!(recoverymode && error)) ! retval = OK; ! ! theend: ! if (curbuf->b_ml.ml_mfp != NULL ! && curbuf->b_ml.ml_mfp->mf_dirty == MF_DIRTY_YES_NOSYNC) ! // OK to sync the swap file now ! curbuf->b_ml.ml_mfp->mf_dirty = MF_DIRTY_YES; ! ! return retval; } #if defined(OPEN_CHR_FILES) || defined(PROTO) *************** *** 2941,2947 **** --- 2955,2964 ---- if (cryptkey == NULL && !*did_ask) { if (*curbuf->b_p_key) + { cryptkey = curbuf->b_p_key; + crypt_check_swapfile_curbuf(); + } else { // When newfile is TRUE, store the typed key in the 'key' *** ../vim-9.0.1582/src/testdir/test_crypt.vim 2023-04-25 15:27:23.355582228 +0100 --- src/testdir/test_crypt.vim 2023-05-27 17:47:35.220670356 +0100 *************** *** 1,5 **** --- 1,6 ---- " Tests for encryption. + source shared.vim source check.vim CheckFeature cryptv *************** *** 88,93 **** --- 89,117 ---- call Crypt_uncrypt('xchacha20v2') endfunc + func Test_crypt_sodium_v2_startup() + CheckFeature sodium + CheckRunVimInTerminal + + let buf = RunVimInTerminal('--cmd "set cm=xchacha20v2" -x Xfoo', #{wait_for_ruler: 0, rows: 6}) + call g:TermWait(buf, g:RunningWithValgrind() ? 1000 : 50) + call term_sendkeys(buf, "foo\foo\") + call term_sendkeys(buf, "ifoo\") + call term_sendkeys(buf, "ZZ") + call TermWait(buf) + + " Wait for Vim to write the file and exit. Then wipe out the terminal buffer. + call WaitForAssert({-> assert_equal("finished", term_getstatus(buf))}) + exe buf .. 'bwipe!' + call assert_true(filereadable('Xfoo')) + + let buf = RunVimInTerminal('--cmd "set ch=3 cm=xchacha20v2 key=foo" Xfoo', #{rows: 10}) + call g:TermWait(buf, g:RunningWithValgrind() ? 1000 : 50) + call StopVimInTerminal(buf) + + call delete('Xfoo') + endfunc + func Uncrypt_stable(method, crypted_text, key, uncrypted_text) split Xtest.txt set bin noeol key= fenc=latin1 *** ../vim-9.0.1582/src/version.c 2023-05-27 14:10:04.319182880 +0100 --- src/version.c 2023-05-27 17:44:31.116746546 +0100 *************** *** 697,698 **** --- 697,700 ---- { /* Add new patch number below this line */ + /**/ + 1583, /**/ -- From "know your smileys": y:-) Bad toupee /// Bram Moolenaar -- Bram@Moolenaar.net -- http://www.Moolenaar.net \\\ /// \\\ \\\ sponsor Vim, vote for features -- http://www.Vim.org/sponsor/ /// \\\ help me help AIDS victims -- http://ICCF-Holland.org ///