6
26
2011
3

使用 LD_PRELOAD 进行文件读写重定向

缘起

换Arch后,我把首选字体改成了这样:

  <match target="pattern">
    <test qual="any" name="family">
      <string>monospace</string>
    </test>
    <edit name="family" mode="prepend" binding="strong">
      <string>DejaVu Sans Mono</string>
      <string>文泉驿等宽正黑</string>
    </edit>
  </match>
  <match target="pattern">
    <test qual="any" name="family">
      <string>serif</string>
    </test>
    <edit name="family" mode="prepend" binding="strong">
      <string>DejaVu Serif</string>
      <string>文泉驿正黑</string>
    </edit>
  </match>
  <match target="pattern">
    <test qual="any" name="family">
      <string>sans-serif</string>
    </test>
    <edit name="family" mode="prepend" binding="strong">
      <string>DejaVu Sans</string>
      <string>文泉驿正黑</string>
    </edit>
  </match>

也就是说,DejaVu 的字体优先于文泉驿的,因为我觉得 DejaVu 的英文字体比文泉驿的好看。这一般没什么问题,除了某些PDF文件,不知道怎么搞的,不嵌入字体也就算了,字体名也奇奇怪怪弄得 evince 不认识。因为太奇怪,并且 evince 显示得更奇怪,我也不好像 KaiTi_GB2312→KaiTi 这样做字体替换了(其实应该用别名的,但当时不会)。想到一个很简单的办法是,让中文字体拥有更高的优先级。实践证明这样做有效。但是,我不希望为了几个乱码的PDF而更改我的全局字体配置。于是,基于 LD_PRELOAD 的解决方案出来了。

LD_PRELOAD 是什么?

LD_PRELOAD 是 Linux 动态链接器认识的一个环境变量(不知道其他系统是否支持)。我看的资料是这个Fun with LD_PRELOAD。原理很简单,使用自己的函数覆盖掉其它动态链接库的。因为 libc 对系统调用都有一个 wrapper,所以正常的程序都会“被骗”的。proxychains 这个让其它程序使用 socks/https 代理的程序的原理就是这个。

我的 hack

我想对指定程序(evince)重定向~/.fonts.conf的读取,所以准备覆盖open这个系统调用。按照从那个PDF的链接中找到的源码的方式,写出了最初的代码。结果用cat测试就失败了——原来还有个 manpage 没说的open64。继续测试,又发现对vi写文件时不起作用。于是加上了第三个要覆盖的函数creat

程序写好后,觉得光重定向一个文件的读写不好玩,稍稍扩展一下,支持配置就强大了。本来想学那个PDF提到的netjail一样用环境变量的。但又觉得不好设计,而且 C 语言真的写起来太麻烦了。于是想到了用 Lua 来配置。后来还想到用 Python 的,但很可惜,段错误了。

#include<stdarg.h>
#include<dlfcn.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<limits.h>
#include<unistd.h>

#include<lua.h>
#include<lualib.h>
#include<lauxlib.h>

static int lib_initialized = 0;
static int (*orig_open)(const char*, int, mode_t) = 0;
static int (*orig_open64)(const char*, int, mode_t) = 0;
static int (*orig_creat)(const char*, mode_t) = 0;
static lua_State *L = NULL;
void lib_init();

void die(char *fmt, ...) {
  va_list args;
  va_start(args, fmt);
  vfprintf(stderr, fmt, args);
  va_end(args);
  fprintf(stderr, "\n");
  fflush(stderr);
  exit(-1);
}

static char* redirect(const char* file){
  int ret;
  const char *new;
  lua_getglobal(L, "redirect");
  lua_pushstring(L, file);
  ret = lua_pcall(L, 1, 1, 0);
  if(ret){
    fprintf(stderr, "取得重定向文件路径时出错了: %s\n", lua_tostring(L, -1));
    lua_pop(L, 1); /* 错误信息 */
    return (char*)file;
  }else{
    new = lua_tostring(L, -1);
    lua_pop(L, 1); /* 返回值 */
  }
  return (char*)new;
}

int open(const char* file, int flags, mode_t mode) {
  lib_init();
  file = redirect(file);
  return orig_open(file, flags, mode);
}

int open64(const char* file, int flags, mode_t mode) {
  lib_init();
  file = redirect(file);
  return orig_open64(file, flags, mode);
}

int creat(const char* file, mode_t mode) {
  lib_init();
  file = redirect(file);
  return orig_creat(file, mode);
}

void lib_init() {
  void *libhdl;
  char *dlerr;

  if (lib_initialized) return;

  if (!(libhdl=dlopen("libc.so.6", RTLD_LAZY)))
    die("Failed to patch library calls: %s", dlerror());

  orig_open = dlsym(libhdl, "open");
  if ((dlerr=dlerror()) != NULL)
    die("Failed to patch open() library call: %s", dlerr);

  orig_open64 = dlsym(libhdl, "open64");
  if ((dlerr=dlerror()) != NULL)
    die("Failed to patch open64() library call: %s", dlerr);

  orig_creat = dlsym(libhdl, "creat");
  if ((dlerr=dlerror()) != NULL)
    die("Failed to patch creat() library call: %s", dlerr);

  int ret;
  L = luaL_newstate();
  luaL_openlibs(L);
  char config[PATH_MAX];
  strcpy(config, getenv("HOME"));
  strcat(config, "/.openredir.lua");
  ret = luaL_dofile(L, config);
  if(ret){
    die("Error run ~/.openredir.lua");
  }
  lua_getglobal(L, "redirect");
  if(!lua_isfunction(L,-1)){
    die("Error run 'redirect' function in openredir.lua");
  }
  lua_pop(L, 1);

  lib_initialized = 1;
}

Makefile 如下:

CC=gcc
CFLAGS=-g -Wall -I/usr/include/lua5.1
LDFLAGS=-ldl -llua

.PHONY: all clean

all: openredir.so

openredir.so: openredir.o
    gcc -shared $< -o $@ $(LDFLAGS)

clean:
    -rm *.o

下面这个是配置文件,要放到~/.openredir.lua才行。

redirect = function(path)
  -- TODO use absolute and normalized path
  io.stderr:write('打开文件 ' .. path .. '\n')
  if path == '/' then
    return '/etc/issue.tty1'
  elseif path == '/home/lilydjwg/.fonts.conf' then
    return '/home/lilydjwg/tmpfs/fonts.conf'
  else
    return path
  end
end

示例:

>>> LD_PRELOAD=./openredir.so cat /
打开文件 /
Arch Linux \r  (\n) (\l)

不过使用时在LD_PRELOAD中最好使用绝对路径,因为某些程序会chdir()到其它地方去的。

最后吐槽下,Lua 里把路径转成 normalize 过的绝对路径都没有现成的函数,囧。。。


2014年11月23日更新:openredir 已经放在 GitHub 上了。

Category: Linux | Tags: C代码 fontconfig linux Lua
6
21
2011
3

Arch 休眠设置

之前因为更改分区把Ubuntu的休眠给弄坏了,现在来到Arch下近一个月了,才发现原来Arch的休眠也有问题——休眠成功了,但是再启动直接重新开始了。内核参数resume=/dev/disk/by-label/swap也加了。Google 一下,很容易就找到了答案——原来这个也要配置!

很简单,在/etc/mkinitcpio.confHOOKS数组里加上resume,然后重新生成 initramfs 即可:

sudo mkinitcpio -p kernel26

有点郁闷,竟然默认不支持,安装系统或者 pm-utils 时也没提示什么的。我之前还以为是内核的resume参数不支持通过磁盘标签指定呢。又找了下,wiki 里倒是有。但是每安装个软件都查查 wiki 也太累了吧,那样还不如回 Ubuntu 呢。

Category: Linux | Tags: arch linux
5
6
2011
9

login shell 和 non-login shell 不同造成的问题

上篇说到,我在Arch下的tmux的部分环境变量有问题。于是接下来我开始调查原因。最后终于真相大白。不过在揭露真相前,先详细说说问题是什么。

自从使用zsh以后,我在Ubuntu下发现我在~/.profile中设置PATH变量的代码在tty下没有起作用。但在~/.zshrc中设置又不行,因为图形界面登录时不会读取~/.zshrc。source 它也不行,因为可能导致双重设置(在一段时间里,我总是很奇怪地发现命令补全时某些命令会出现两次。。。)。于是,最后我的方案是这样的(箭头表示 source):

.profile --> .zsh/zshrc.env   <-+
.zshrc --> ZSHRC_ENV set? --No--+

这个方案在Ubuntu下一直工作良好。但在Arch+tmux下就出问题了。在tmux中的zsh启动前,ZSHRC_ENV已经设置,于是~/.zsh/zshrc.env没有被 source,于是$PATH设置得不对了。。。

在查阅tmux N次之后,我想,可能是某个启动文件覆盖了我自己的 PATH 变量的设置。于是打开 zsh 的文档,翻到这里:

5.1 Startup/Shutdown Files

Commands are first read from /etc/zsh/zshenv; this cannot be overridden. Subsequent be- haviour is modified by the RCS and GLOBAL_RCS options; the former affects all startup files, while the second only affects global startup files (those shown here with an path starting with a /). If one of the options is unset at any point, any subsequent startup file(s) of the corresponding type will not be read. It is also possible for a file in $ZDOTDIR to re-enable GLOBAL_RCS. Both RCS and GLOBAL_RCS are set by default.

Commands are then read from $ZDOTDIR/.zshenv. If the shell is a login shell, commands are read from /etc/zsh/zprofile and then $ZDOTDIR/.zprofile. Then, if the shell is interactive, commands are read from /etc/zsh/zshrc and then $ZDOTDIR/.zshrc. Finally, if the shell is a login shell, /etc/zsh/zlogin and $ZDOTDIR/.zlogin are read.

于是发现这一切的根源在于tmux里启动的是login shell!Arch的/etc/profile中重置了$PATH/etc/profile.d/locale.sh中重置了$LANG,所以造成我的tmux下的zsh环境变量不对的问题。于是我把设置移回了~/.profile中,然后将软链接~/.zprofile指向它。locale.shpacman不知道是什么包的,所以我就把它改成了:

[ -z "$LANG" ] && export LANG=en_US.UTF-8

至此,tmux部分的问题终于解决了!

Category: Linux | Tags: arch linux zsh tmux
4
7
2011
9

带补丁gvim 7.3 Arch软件包下载

准备向 Arch 迁移了,于是编译了个GVIM。之所以要自己编译,当然是要打非官方补丁了。

主要的 bug 修正为:

其中第三条只在 Arch 上出现,据说是某个库的 bug。后来折腾了段时间,发现如果 gvim 不 fork,或者 fork 后父进程生存的时间长一点点,就不会错误地出现这个提示(但是真正使用超级用户权限时也没有这个提示,不知道是否属正常情况)。


2011年7月14日更新:最新版下载地址

Category: Vim | Tags: arch C代码 linux vim
4
1
2011
3

五发行版联合发布新的发行版——The Canterbury Distribution

今天收到 arch-announce 邮件列表的消息,Arch Linux, Debian, Gentoo, Grml and openSUSE 联合发布 The Canterbury Distribution 了~~不信?那访问它们的主页试试:

PS: Arch 做了点小手脚哦~


Happy April Fool's Day!

Category: Linux | Tags: linux joke
3
19
2011
10

Ubuntu下折腾分区后休眠不能唤醒问题的解决

我自以为把 Linux 的分区相关的东西折腾得比较熟了,所以就大胆地在本机上折腾分区,结果有一天就发现,在我在 /etc/fstab 里把 swap 区从UUID指定改成用LABEL指定之后,休眠可以成功,但唤醒失败。在开机后应当从 swap 分区恢复的时候,出现错误,然后就直接启动了,其间还毁掉了 swap 分区的内容。这么试过了两次,都失败了,于是我带着迷惑,再也没有试过了。

前天在群里看到这个链接,终于恍然大悟——原来还有个配置文件/etc/initramfs-tools/conf.d/resume!它指明了唤醒时从哪个分区恢复。当初改 swap 后我只改了fstab但没有改它,所以可以正常使用却不能唤醒。不过重新生成 initrd 时为什么没有自动更新 resume 文件呢?而且休眠时系统也不检查下resume文件,郁闷。不知道休眠时能不能指定休眠到哪个分区上呢?

resume文件的内容相当简单,就一行,像这样RESUME=LABEL=swapRESUME=后面的格式和 fstab 的第一栏一样。改了后还要sudo update-initramfs -u更新 initrd。

另外,唤醒不成功的时候我曾尝试过 s2disk 这个工具,但昨天发现,虽然它在休眠时有进度显示,可是在唤醒的时候失败了——当进度达到 100% 时就没动作了,我的桌面也就没能回来……

Category: Linux | Tags: linux ubuntu
3
16
2011
22

关于“Linux”被翻译为“你牛叉”等的一些想法

昨天,我就在我所建立的vim-cn Gtalk群上看到了在Debian官方简体中文首页将“Debian”翻译为“蝶变”、“Linux”翻译为“你牛叉”,当时还以为是某人的恶作剧。今天,又见到这篇文章,才知道真相。

最开始读的时候,看到大量的翻译破坏,我想到是不是使用的机器翻译,随后意识到不可能。又以为是Linux反对者的发泄。但我还是错了。

做这件事的人是“沈卓斌”,在多个 Wiki 上的 ID 是“jobinson99”。他除了做这些莫名其妙的翻译更改外,也做了一些妥当的翻译,让恢复变得困难起来。我在ArchLinux上的几个页面改掉了他的一些翻译,更多的翻译还得依靠Google来找出了。在改的过程中我发现部分页面有相互复制的情况。不过我没精力管了。至于维基百科,其中文社区比较强大,针对其的破坏已经被恢复。其他的Debian啊、FreeBSD啊我就没心思去管了。

有些人可能对此不太在意,但我很早就读了中文维基百科的一些方针指引之类的,很快便认定这是“破坏行为“。尽管他的这些翻译看起来是善意的,但是其行为却不会被接受。首先不说他的翻译是否准确恰当,首先一点就是——没有共识。我认为,再好的翻译,只有极少数人知道,它也不应当被作为通用或者正式的名称,不然,就像使用世界语一样,大部分人根本就不知道你在说什么,更别说搜索引擎了。哪天你有某个Linux问题,Google数日未解决,却蓦然发现,原来写有解决方案的Wiki中使用的是“你牛叉”而导致你没有找到,岂不郁闷!更何况,这些翻译多似恶搞,不是所有人都会接受的,我最开始就以为是反对者来着。

我很不明白,各开源项目的维基上尚有大量资料需要翻译,中文 man 手册既少又旧以至于我都不敢用了,wget 的中文翻译错误虽然在 Ubuntu 下已经更正了,但在 ArchLinux 下依旧存在。还有这么多可以做的,他却以为无聊的翻译可以推广开源?新手安装 Linux,谁不需要安装指导?遇到问题了,为什么可能的解决办法都是用英文写的?命令不会用,man 一下,为什么没有中文翻译?很多新手在尝试Linux时都遇到各种困难而放弃,很多老鸟都在遇到问题时苦苦挣扎,我多么希望所有的开源资料能有中文翻译!很多人说Linux难用。是啊,不会英语又没人指导就根本无法使用的系统怎么会好用呢?

至于jobinson99,我想说,如果你真的蛋疼得不行的话,就把翻译放伪基百科上去啊。


附:我在 ArchWiki 上发现的不妥翻译列表:

%s/开天辟地你牛叉/LFS/ge
%s/你牛叉龙骨/ArchLinux/ge
%s/龙骨/Arch/ge
%s/你牛叉/Linux/ge
%s/共努/GNU/ge
%s/荟萃/Wiki/ge
%s/有奔头/Ubuntu/ge
%s/蝶变/Debian/ge
%s/合成/编译/gce
%s/优盘/U盘/gce

 

Category: Linux | Tags: linux 中文支持
3
12
2011
3

果断放弃 pidgin 及解决 empathy 打开链接极度缓慢的问题

不知从什么时候起,我发现直接点击 Empathy 中的链接会花较长时间,在此期间整个 Empathy 的所有窗口皆冻结而不响应操作。次数多了之后,我注意到等待的时长和链接的响应时间有关,想到曾经在HTTP服务器看到的包含 gvfs 字样的访问记录,我想就是它了。但除了发现有一个杀死又复活的 gvfsd-http 进程之外别无收获。

昨天不知道究竟是什么原因,Empathy 经常反应迟钝,于是我终于换 pidgin 了。两天已经过去,我又换回 Empathy 了,虽然 pidgin 有很多的优点,如截图、HTML 消息、联系人手动排序、*粗体* 和 _斜体_ 支持、重新发送好友请求、XMPP 控制台、好友响应可能性预测、自动弹出消息窗口、链接识别更准确、支持富文本格式的剪贴板、可取消选择区内的表情符号、支持终端(finch)、各种未知的插件等等,但是,我依旧要换 Empathy 了,因为 pidgin 的几个非常影响我使用的 bug。一是点击对话窗口,焦点不会自动移动到输入区,虽然依旧可以输入文字,但绕过了输入法,所以必须按下 Tab 或者点下输入区,否则无法输入中文。二是输入区文本选中后会清除选择区剪贴板的内容,但并不把选中文本放到剪贴板中。其三,也是我最不能忍受的是,在它的输入框输入文本时无法使用 fcitx 的第二三码选词键。

回到 Empathy,于是又要面对它打开链接缓慢的问题。虽然改用选中再粘贴的方式也不太费事,但是,看到带有下划线的浅蓝色链接难道你就不会下意识地去点吗?

Google 了很久,没找到任何有用的东西。于是开始 strace——

strace -p `pgrep empathy` -r -f

结果用 sort + tail,很容易看到最耗时的地方。我之前还在 Vim 中手动看那近一万行的记录,真是被这些问题搞晕了头了。。。

[pid  6130]      0.000149 connect(27, {sa_family=AF_FILE, path=@"/dbus-vfs-daemon/socket-jCww54HT"}, 35) = 0
...
[pid  6130]     46.212892 recvmsg(27, {msg_name(0)=NULL, msg_iov(1)=[{"l\3\1\1\"\0\0\0\1\0\0\0G\0\0\0\4\1s\0)\0\0\0org.glib"..., 2048}], msg_controllen=0, msg_flags=MSG_CMSG_CLOEXEC}, MSG_CMSG_CLOEXEC) = 122

gvfs 毫无疑问是元凶了。接收消息竟然用了 46 秒!后来我又想到去 strace gvfsd-http,最终发现真相——gvfsd-http 在 HEAD 那个 URL!真不知它想做什么。用 dpkg 查到它属于 gvfs-backends 这个软件包。看了下,依赖它的只有 deb-gview,几乎从来没用过的东西。于是果断卸载之。

卸载完后,回过头把 gvfsd-http 杀掉,终于敢放心大胆地点击 Empathy 中的链接了。

Category: Linux | Tags: Empathy linux
2
27
2011
9

Wine QQ隐私保护器

鉴于QQ 4 Linux实在太不好用,有些不得不使用QQ的Linux用户使用Wine来运行QQ,我经过努力也成功了,但依旧不用,因为在去年的3Q大战中我们看到腾讯实在难以信任。想想我的ssh密钥、GPG密钥、getmail和msmtp的配置文件等等非常重要的文件QQ皆可轻易扫描并上传……

想过使用另外的低权限用户来wine QQ。然而即使这样,我自己的众多文件QQ依然可以看到,心中还是不安,而且这样作为QQ的重要功能之一——互传文件——将很不方便。于是终于有一天,我开始折腾chroot了。为了不需要总是sudo,我用C写的,可以使用Linux的suid特性,同时也练习下C编程。在man相关系统调用的时候,我还发现了unshare这个调用,竟然可以把挂载的文件系统设置成只在新的挂载命名空间(mount namespace)中可见。这样就可以把为了chroot而mount --bind的项目全部隐藏起来,免得mount输出一大堆不想看到的东西。至于umount嘛,不管了,反正只是mount --bind的。

/* ===================================================================== *
 * chrooted wine QQ
 * ===================================================================== */
#define _GNU_SOURCE
#include<unistd.h>
#include<sys/stat.h>
#include<sys/types.h>
#include<sys/mount.h>
#include<linux/limits.h>
#include<sched.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>
#define ROOT "/tmp/.rwine"
#define PROGRAM "你的QQ的目录"
/* 文件共享目录;若不存在会自动创建 */
#define SHAREDIR "哪个目录作为共享?"
#define BIN "Bin/QQ.exe"
#define ROOTD(dir) ROOT dir
#define MKDIR(dir) mkdir(ROOTD(dir), 0777)
#define MOUNTBIND(dir) mountbind(dir, ROOTD(dir))
int mountbind(const char *source, const char *target);
/* --------------------------------------------------------------------- */
int main(int argc, char **argv){
  int ret;
  ret = unshare(CLONE_NEWNS);
  if(ret){
    perror("unshare");
    exit(1);
  }
  seteuid(getuid());
  ret = mkdir(ROOT, 0777);
  seteuid(0);
  if(ret && errno != EEXIST){
    perror("mkdir new root dir");
    exit(1);
  }

  seteuid(getuid());
  mkdir(SHAREDIR, 0700);
  seteuid(0);
  setgid(0);
  MKDIR("/lib");
  MKDIR("/usr");
  MKDIR("/usr/share");
  MKDIR("/usr/bin");
  MKDIR("/usr/lib");
  MKDIR("/etc");
  MKDIR("/etc/fonts");
  MKDIR("/program");
  MKDIR("/dev");
  MKDIR("/tmp");
  MKDIR("/tmp/share");
  chmod(ROOTD("/tmp"), 01777);

  ret = MOUNTBIND("/lib")
    || MOUNTBIND("/usr/share")
    || MOUNTBIND("/usr/bin")
    || MOUNTBIND("/usr/lib")
    || MOUNTBIND("/etc/fonts")
    || MOUNTBIND("/dev")
    || mountbind(PROGRAM, ROOTD("/program"))
    || mountbind(SHAREDIR, ROOTD("/tmp/share"));
  if(ret){
    perror("mount --bind");
    exit(1);
  }

  char path[PATH_MAX];
  char path2[PATH_MAX];
  char *p;
  strcpy(path, getenv("HOME"));
  strcat(path, "/.wine");
  strcpy(path2, ROOT);
  p = strtok(path, "/");
  seteuid(getuid());
  while(p){
    strcat(path2, "/");
    strcat(path2, p);
    mkdir(path2, 0777);
    p = strtok(NULL, "/");
  }
  seteuid(0);
  /* path changed by strtok */
  strcpy(path, getenv("HOME"));
  strcat(path, "/.wine");
  strcpy(path2, ROOT);
  strcat(path2, path);
  ret = mountbind(path, path2);
  if(ret){
    perror("mount --bind user's wine dir");
    exit(1);
  }
  strcpy(path, getenv("HOME"));
  strcat(path, "/.fonts");
  if(access(path, X_OK) == 0){
    strcpy(path2, ROOT);
    strcat(path2, path);
    seteuid(getuid());
    mkdir(path2, 0777);
    seteuid(0);
    ret = mountbind(path, path2);
    if(ret){
      perror("mount --bind user's font dir");
      exit(1);
    }
  }
  strcpy(path, getenv("HOME"));
  strcat(path, "/.fontconfig");
  if(access(path, X_OK) == 0){
    strcpy(path2, ROOT);
    strcat(path2, path);
    seteuid(getuid());
    mkdir(path2, 0777);
    seteuid(0);
    ret = mountbind(path, path2);
    if(ret){
      perror("mount --bind user's fontconfig dir");
      exit(1);
    }
  }

  ret = chroot(ROOT);
  if(ret){
    perror("chroot");
    exit(1);
  }
  chdir("/tmp");
  ret = setuid(getuid());
  if(ret){
    perror("setuid");
    exit(1);
  }
  execl("/usr/bin/wine", "/usr/bin/wine", "/program/"BIN, NULL);
  /* execl("/usr/bin/wine", "/usr/bin/wine", "notepad", NULL); */
  perror("execl");
  return 1;
}
int mountbind(const char *source, const char *target){
  return mount(source, target, NULL, MS_BIND | MS_NOSUID, NULL);
}
/* ===================================================================== *
 * vim modeline                                                          *
 * vim:se fdm=expr foldexpr=getline(v\:lnum)=~'^\\S.*{'?'>1'\:1:         *
 * ===================================================================== */

程序名为rwine。这里的“r”可不是rsync或者rcp中的“r”,而是“rbash”、“rzsh”、“rvim”中的“r”的意思。在代码的最开头有几个宏是用来配置路径的。suid程序嘛,像这种东西还是硬编码进去好了。因为只是自己使用,所以没有做太多的错误检查之类的,健壮性应该不怎么样。对于这个程序我只要没有安全漏洞就OK了。

另外说个小插曲。在调试这段代码的时候,我发现用户的路径总是挂载得不对,检查了好久,才注意到strtok的声明是

char *strtok(char *str, const char *delim);

第一个参数那里没有const!再仔细看看man文档,果然,它把传进去的参数给改了。

Category: Linux | Tags: C代码 linux QQ 腾讯
1
1
2011
47

让Vim在图形界面与终端中的Alt组合键相同

首先祝大家新年快乐!


一直都感觉Vim下快捷键不够用,于是在某一天,我开始使用Alt开头的组合键,然后发现了问题——

在很多终端中,Alt 组合键发送的是 Esc 前缀键码,而图形界面中则是置位最高位。举例来说,Alt-x在图形界面下向Vim发送的是ø(在Vim插入模式下使用Ctrl-V Alt-x可以看到),其编码为0xf1,而x的编码为0x78,区别在于前者二进制编码的最高位是 1,而后者是 0。

而在gnome-terminal、konsole中则是另外一番景象。Alt-x和快速地按Esc x的效果是一样的,仅有xterm 和 rxvt 等终端可选地支持像图形界面的那样处理(参见Vim手册:help :map-alt-keys)。而且,使用置位最高位的终端将导致shell中的Alt-f之类的键绑定失效。

Emacs能处理这种不一致,但Vim不能,于是我一直是使用脚本,使得在终端下和图形界面下使用不同的键绑定。这样图形界面下没什么问题,但终端下比较郁闷:因为映射了Esc开头的键,而Esc是用于回到普通模式的,于是每次按Esc想退回到普通模式时都得等一秒('timeoutlen的值)。这个值又不能设小,不然\ww这种需要多次按键的映射就难用了。

前些天,偶然在帮助文档里看到了这个:

					*:set-termcap* *E522*
需要 {option} 的地方,可以使用 "t_xx" 形式来设置终端选项。这些选项覆盖相应的
termcap 值。设置后,可以用于映射。如果 "xx" 包含特殊字符,须用 <t_xx> 形式: >
	:set <t_#4>=^[Ot
也可用来翻译普通键的特殊键码。例如,如果 Alt-b 产生 <esc>b,可用: >
	:set <m-b>=^[b
(这里 ^[ 是真正的 <esc>,用 CTRL-V <esc> 来输入)
这个方法优于映射之处在于它能适用于所有情况。

也就是说,可以在终端下把Alt组合键都设置到Esc开头的键码,这样一是不用每次设置键映射时设置两个,更重要的是,其本质变了:这样的设置不是键映射,而是指定键码!这样会使用'ttimeoutlen'的值来等待后续键码,和映射无关了,我完全可以把它设置得很小。于是写出新的脚本:

 1 " escalt.vim    控制台下让用 <M-x> 也可用
 2 " Author:       lilydjwg <lilydjwgATgmail.com>
 3 " Last Change:  2010年12月15日
 4 " ----------------------------------------------------------
 5 " Load Once:
 6 if &cp || exists("g:loaded_escalt") || has("gui_running")
 7   finish
 8 endif
 9 let s:keepcpo = &cpo
10 let g:loaded_escalt = 1
11 set cpo&vim
12 " ----------------------------------------------------------
13 " Functions:
14 function Escalt_console()
15   for i in range(65, 90) + range(97, 122)
16     exe "set <M-".nr2char(i).">=\<Esc>".nr2char(i)
17   endfor
18   set ttimeoutlen=50
19   if &term =~ 'xterm'
20     set <F1>=^[OP
21     set <F2>=^[OQ
22     set <F3>=^[OR
23     set <F4>=^[OS
24     set <Home>=^[OH
25     set <End>=^[OF
26   endif
27   for i in ["", "c", "i", "x"]
28     exe i . "map Ï1;2P <S-F1>"
29     exe i . "map Ï1;2Q <S-F2>"
30     exe i . "map Ï1;2R <S-F3>"
31     exe i . "map Ï1;2S <S-F4>"
32   endfor
33 endfunction
34 " ----------------------------------------------------------
35 " Call Functions:
36 call Escalt_console()
37 " ----------------------------------------------------------
38 "  Restoration And Modelines:
39 let &cpo= s:keepcpo
40 unlet s:keepcpo
41 " vim:fdm=expr:fde=getline(v\:lnum-1)=~'\\v"\\s*-{20,}'?'>1'\:1

注意到其中对于F1到F4等键进行了特殊的设置。没办法,这几个键特殊,这样设置我觉得是最优的解了。设置'ttybuiltin'也可以,但是经过一些时间的试用后发现有副作用,具体是什么我忘记了。


PS: SyntaxHighlighter 不支持 Vimscript,还好 Vim 有TOhtml命令。

最新脚本在 GitHub 有。直接复制上面高亮过的代码是不行的。

Category: Vim | Tags: linux vim

Mastodon | Theme: Aeros 2.0 by TheBuckmaker.com