5
15
2014
2

使用 udev 规则自动配置 IP 地址

udev 规则其实挺简单的,但第一次配置也颇费了一番工夫。

事情的起因是这样子的。我的手机,还有 Kindle Paperwhite,都能接电脑上提供一 USB 网络设备,可以用来 ssh 啊 rsync 啊什么的。但是呢,每次接好之后还要执行条命令设置 IP 地址,还要用 sudo、输入密码,很是麻烦。

我用来配置 IP 地址的命令是:

ifconfig usb0 192.168.42.1 # 手机
ifconfig usb0 192.168.15.1 # Kindle

查阅 udev(7) man 文档之后,对 udev 规则有了大概了解,知道大约要写成以下形式:

ACTION=="add", SUBSYSTEM=="net", XXX, RUN+="xxx"

需要一个属性来确定添加的设备是目标设备。插入设备,使用udevadm命令来检查设备的各种属性:

udevadm info --attribute-walk /sys/class/net/usb0

本来准备用 MAC 地址的,但后来才发现我这 Android 手机每次的 MAC 地址都不一样。想到 adb 用的序列号,于是我决定用ATTRS{serial}=="BX90345MWH"。然后轮到 Kindle 了。结果一看,竟然没有序列号属性了……但是它的 MAC 地址不会变,所以用 MAC 地址了。

写好规则之后可以先测试一下:

udevadm test /sys/class/net/usb0

配置正确的话会看到一行以run:开头的行里写着自己定义的命令。

没问题就让 udevd 重新加载规则文件:

sudo udevadm control --reload-rules

到这里似乎就该结束了。可事与愿违,测试都没问题了,但 IP 地址就是没出现。查阅各处文档,也没做错什么呀。后来才注意到测试时上边有一行输出:

run: '/usr/lib/systemd/systemd-sysctl --prefix=/proc/sys/net/ipv4/conf/usb0 --prefix=/proc/sys/net/ipv4/neigh/usb0 --prefix=/proc/sys/net/ipv6/conf/usb0 --prefix=/proc/sys/net/ipv6/neigh/usb0'

它使用的是绝对路径!想起 systemd 的命令必须是绝对路径,我尝试改成绝对路径,果然可以了:

ACTION=="add", SUBSYSTEM=="net", ATTRS{serial}=="BX90345MWH", RUN+="/bin/ifconfig %k 192.168.42.1"
ACTION=="add", SUBSYSTEM=="net", ATTR{address}=="ee:49:00:00:00:00" RUN+="/bin/ifconfig %k 192.168.15.1"
Category: Linux | Tags: linux udev
4
25
2014
4

批量给图片加不同位置的水印

再次成功 root Kindle 之后,突发奇想:把自己的 ID 和二维码显示在其上应该会很有趣。生成二维码用 zint 搞定,添加上 ID 啦阴影啦边缘半透明啦用 GIMP 搞定。可是,我想把自带的 20 张屏保图片全部加上这个小图呢?我当然可以用 ImageMagick 批量添加,但是定位呢?对于不同图片,我要把这二维码放到不同的地方呢!于是终于拿 GTK 2 写了这么个很久以前就想写的程序——imagestamp

功能很简单,用法也很简单,make之后直接执行:

./imagestamp -p my_qr_code.png *.png

然后在弹出的窗口里不断地在适当的位置点击即可 :-)

下边是图片之一。由于对 Kindle 的像素密码估算不足所以那个二维码小了一点。

修改之后的 Kindle 屏保图片

Category: Linux | Tags: 图片 gtk kindle
4
24
2014
8

截图并识别二维码

现在到处都是二维码,于是经常看到某网页有个二维码,又懒得拿出手机开扫码程序来扫。于是有了这个方案:ImageMagick 截图,zbar 识别:

import png:- | zbarimg /dev/stdin

二维码这东西的泛滥是在把人的识别能力拉到与计算机齐平么……什么时候,我能够直接对着一网址扫过去,浏览器就能拿到那个网址并打开呀。

Category: Linux | Tags: QR code ImageMagick
4
16
2014
62

Sublime Text 能做的,Vim 都能做到吗?

前两天突发奇想,想知道现在越来越流行的 Sublime Text 除了易于上手外,和 Vim 相比还有哪些独到的特性呢?

于是我在 SegmentFault 上提出了这么一个问题,最终的答案整理如下。括号中是项目数,由很简单的 Vim 命令统计或者目测完成。

Sublime Text 能做的,Vim 在不做任何配置、或仅作极少量的配置也能做到的(16):

ST: 随时保留文件的修改 / 自动保存。
Vim:
  1. 当意外退出时,默认在最后四秒内的最多200字符可能会丢失。这两个数值都是可以配置的。
  2. 设置 'autowriteall',在需要保存文件时会自动保存文件。
ST: 按 HTML 标签选择。
Vim: vit, vat, v2it 等。各种文本对象。
ST: CSS 的属性名称自动完成。
Vim: CSS 属性名称和值的智能补全。
ST: 会话保持。
Vim: :mksession 以及 :source。当然很容易配置成自动化的。
ST: 跨平台。
Vim: 把 ST 支持平台数平方一下。
ST: 变身:Vim 模式。
Vim: 变身:简易模式。
ST: 多文件搜索。
Vim: :vimgrep 命令。还有 grep.vimag.vim
ST: 在跳转框内使用 : 到达指定行。
Vim: 行号+G 或者 :行号(并回车)。
ST: 并列显示多个文件。
Vim: 不仅支持各种布局的多窗口,还支持多标签页。
ST: 界面干净。
Vim: guioptions 想去掉什么就去掉什么。
ST: 简易的编辑快捷键,如删除单词删除行头行尾。
Vim: daw, d^, d$,还想要什么?。
ST: 早期版本底部多出一个版面的空间,可以把代码最后的几千显示在窗口上半部分。
Vim: z. 把当前行放到窗口中间。Ctrl-E / Ctrl-Y 来滚动显示的行。
ST & Vim: 已拼单词提示/关键字补全。自动缩进。快捷键。命令行。

Sublime Text 能做的,Vim 在安装额外的插件或者适当配置后也能做到的(8):

ST: 快捷键切换注释。
Vim: 多个插件都可以,如 EnhCommentify
ST: 按照文件名称首字母搜索。
Vim: 多个插件都可以,如 LustyExplorer(按文件名子序列搜索)、Command-T(在当前目录下递归地搜索)。
ST: HTML片断快速插入。
Vim: neosnippet、snipMate 等。
ST: Livereload 插件,网页修改后可直接查看效果。
Vim: 使用 BufWritePost 事件通知浏览器刷新。或者直接使用 inotify 等技术监听文件更新。
ST: 侧栏管理项目目录。
Vim: 自带 netrw 插件、更强大的 NERD tree
ST: 多行移动。
Vim: 进可视模式d了再到需要的位置p就可以,不过还是有些麻烦。可以把以下配置复制到你的 vimrc 中:
" 上下移动一行文字
nmap <C-j> mz:m+<cr>`z
nmap <C-k> mz:m-2<cr>`z
vmap <C-j> :m'>+<cr>`<my`>mzgv`yo`z
vmap <C-k> :m'<-2<cr>`>my`<mzgv`yo`z
ST: 括号自动匹配。
Vim: 有几个插件。我不推荐使用这种功能。
ST & Vim: Zencoding。

Sublime Text 能做的,但是 Vim 还不能完全做到的(5):

ST: 多重光标选择,同时修改。
Vim: 列模式。正则替换。. 重复单个命令。使用宏来重复连续的多条命令。
ST: 简单全面的插件体系。
Vim: vim-addon-manager、Vundle、Bundle 等。
ST: UI 清亮。
Vim: 非 Windows 平台上的 GUI 版本也挺不错。Windows 版本的字体渲染很糟糕。
ST: 按名称搜索命令并执行。
Vim: 传统的命令行补全。
ST: 快。
Vim: 少装些插件,避免使用复杂的语法高亮。

Sublime Text 能做的,但是 Vim 还不能做到的(5):

ST: 代码地图 / Minimap。
Vim: 无。不过有好些人表示 taglist / tagbar 之类的比这个好看的缩略图更中用。
ST: 「经常友好地弹一个窗口,提醒用户付费。」
Vim: 默认在启动时会希望你给乌干达的儿童捐款。
ST: 「如果作者不幸挂掉,将会有一大波程序员陷入悲痛。」
Vim: 如果作者不幸挂掉,将会有一大波程序员尝试成为新的维护者。
ST: Linux 下的中文显示可能有问题,不支持输入法。
Vim: 如果其它程序中没有问题,那么在 Vim 中也不会有问题。
ST: 小巧。安装后大小:14277.00 KiB。
Vim: 带 GTK 2 图形界面、多种解释器支持的巨型版本安装后大小:28056.00 KiB。

后记

我当然是不会换用 Sublime Text,除非其出正式版本并对用户的数据表现出一个负责的态度

markdown 不支持描述性列表好讨厌啊,逼我用 Vim 的宏来修正么 :-( 以后这种复杂的内容还是部分用 HTML 吧,简单的部分用 markdown 写了再通过 Vim 的「filter」功能送给 markdown 好了。

本页用到了仅火狐 21+ 支持的 HTML 5 特性:Scoped CSS

Category: Vim | Tags: vim Sublime Text
4
14
2014
6

Linux 3.14: 终于能方便地看到真正的系统可用内存了

直接取/proc/meminfo中的「MemAvailable」项即可:

awk '$1 == "MemAvailable:" { print $2 * 1024 }' /proc/meminfo | filesize

filesize 是我自己写的将字节数转成人可读形式的脚本。

使用free命令的版本:

free | awk 'NR == 3 { print $4 * 1024 }' | filesize

并不准确,因为已缓存(Cached)内存并不一定是可以释放的,比如我用的 tmpfs 里的数据也算进去了。详见内核的这个提交。「free命令的算法在十年前还不错」,这不就是我大学课程教授的知识所处的时代么? :-D

Category: Linux | Tags: Linux
3
31
2014
6

微信内置浏览器调用微信 OAuth 授权获取用户基本信息的方法

首先,我建议各位打印一份微信官方的「网页授权获取用户基本信息」文档,但是不要阅读它。烧掉它,这有重要的象征意义。

一个位于微信内置浏览器内的网页要获取用户基本信息,首先要获取任意一个用户对其应用的所谓「openid」(其实应该叫「private id」,因为同一个用户在每个应用里的这个 ID 都不一样)。这个可以通过只能获取到「openid」的snsapi_base授权得到。然后,将用户重定向于以下地址:

url_fmt = '''
https://open.weixin.qq.com/connect/oauth2/authorize?
appid={appid}
&redirect_uri={redirect_uri}?userid={userid}
&response_type={response_type}&scope={scope}&state={state}
'''.replace('\n', '')

scope当然是snsapi_userinfo,其它参数自己填。redirect_uri最好通过urllib.parse.quote编码一下,虽然好像不编码也可以用。利用url_fmt.format_map或者url_fmt.format方法把参数填进去,重定向之后,微信就会弹出授权提示页面了。

记住,一定要用我这里给出的格式,不要按标准 URL 参数处理方法使用urllib.parse.urlencode方法构造参数部分,因为这个微信授权 URL 里的参数顺序是重要的。具体规则不清楚,大概是appid一定要在redirect_uri前边,scoperesponse_type一定要在其后边。反正你按我这里给出的格式填就对了。

很奇怪为什么不能使用标准 URL 参数处理函数么?那我还告诉你,给微信接口传 JSON 时,标准 JSON Unicode 转义\uXXXX是无效的。

Category: 网络 | Tags: 腾讯 微信
3
27
2014
7

火狐远程调试初体验

开始啦

首先要启用远程调试功能。在桌面端火狐里按快捷键Ctrl-Shift-K调用开发者工具,点左上角的「设置」按钮,勾选「启用远程调试」。移动端火狐也通过「设置」页启用远程调试。

启用远程调试

设置端口转发。使用 USB 连接并且在 Android 设备上启用了 adb 的话,可以使用如下命令来转发:

adb forward tcp:6000 tcp:6000

我使用 Wi-Fi 网络连接。因为移动端火狐只监听了 127.0.0.1 这个地址,所以外边连不上去。我使用 socat 命令来转发一下。我编译的 Android 版 socat 程序可在这边下载

socat tcp-listen:6000,fork,bind=192.168.1.XXX,reuseaddr tcp:127.0.0.1:6000

桌面端在「Web 开发者」菜单里选择「连接…」,然后填入移动端的 IP 地址。如果使用 adb 进行端口转发的话使用默认的「localhost」就可以了。

连接到远程设备

开始连接之后,被连接的火狐(这里即移动设备上那个)会弹窗询问是否允许。确认之后就可以看到远程设备的标签页以及 chrome 页面(即截图里那个「主进程」)了。

选择连接到的标签页

然后就跳出来一个新的开发者工具窗口了。我这里选择的是火狐自己那个 chrome 页面(「主进程」):

调试移动端火狐

大家可以看到,我在 Android 上的火狐上也安装了 Adblock Plus 哦~

小惊喜

桌面端火狐启用远程调试chrome 调试后,可以在「Web 开发者」菜单里看到「浏览器工具箱」这么一项。它会开启一个-P参数为default-chrome-debugger-chrome参数为chrome://browser/content/devtools/framework/toolbox-process-window.xul的新火狐实例,通过远程调试接口连接到当前火狐实例上,实现对火狐顶层 chrome 窗口的调试。不过直接执行在 htop 里看到的命令并不能开启调试器,大概是因为这时候需要被调试的火狐不知道有人要调试它吧。虽然火狐自带的开发者工具功能比较弱,不过能对顶层 chrome 窗口进行调试还是很不错的 :-)

参考资料

Category: 火狐 | Tags: Android 火狐
3
26
2014
18

百度登陆方法及网盘 API 基本操作

本文只介绍流程,因此是以最方便试错的 shell 脚本为示例的。也就是一系列简单的 HTTP 请求,用什么语言都一样。

要实践本文中的例子,首先要确保系统上已经安装了如下软件:

  • curl: 命令行 HTTP 调试首选工具
  • jshon: 命令行 JSON 解析器。使用简单的栈式语法
  • json_pp: 这个命令行工具是 perl 自带的,把 JSON 数据格式化显示用的

首先把用户信息存到变量里去:

$ BDUSER=你的百度登陆名
$  PASS=你的百度登陆密码

访问一次百度,取得一个名为BAIDUID的 cookie。我们在此,以及以下所有 curl 命令中,都会使用-b-c选项告诉 curl 从当前目录下的「cookies」文件读取 cookie 数据,把接收到的 cookie 写到同一个文件里去。

$ curl -b cookies -c cookies http://www.baidu.com/ -sS -o /dev/null

获取 token:

$ TOKEN=$(curl -b cookies -c cookies -sS "https://passport.baidu.com/v2/api/?getapi&tpl=mn&apiver=v3&class=login&tt=$(date +%s860)&logintype=dialogLogin" | tr "'" '"' | jshon -e data -e token -u)
$ curl -b cookies -c cookies "https://passport.baidu.com/v2/api/?logincheck&token=$TOKEN&tpl=mn&apiver=v3&tt=$(date +%s)&username=$BDUSER&isphone=false"
{"errInfo":{ "no": "0" }, "data": { "codeString" : "", "vcodetype" : "" }}

使用用户信息登陆:

$ curl -b cookies -c cookies --compressed -sS 'https://passport.baidu.com/v2/api/?login' -H 'Content-Type: application/x-www-form-urlencoded' --data "staticpage=http%3A%2F%2Fpan.baidu.com%2Fres%2Fstatic%2Fthirdparty%2Fpass_v3_jump.html&charset=utf-8&token=$TOKEN&tpl=mn&apiver=v3&tt=$(date +%s083)&codestring=&safeflg=0&u=http%3A%2F%2Fpan.baidu.com%2F&isPhone=false&quick_user=0&logintype=basicLogin&username=$BDUSER&password=$PASS&verifycode=&mem_pass=on&ppui_logintime=57495&callback=parent.bd__pcbs__ax1ysj" | grep -F 'err_no=400032' > /dev/null

如果这条命令返回(即$?的值)为 0 则成功,否则失败。这是因为百度的登陆之后的页面会进行跳转,如果登陆成功那么跳转 URL 里包含err_no=400032,否则err_no是别的什么值。这个判断条件可能会变化,比如去年年底是err_no=40032时表示成功。

登陆成功之后就可以调用网盘 API 了。这部分比登陆的请求要好看许多。

先来获取一下网盘容量,也好确认我们确实登陆成功了:

$ curl -b cookies -c cookies 'http://pan.baidu.com/api/quota'
{"errno":0,"total":510341939200,"used":0}

这些请求直接在网页版里开着 Firebug 看就可以了。比如:

列根目录下的文件信息:

$ curl -b cookies -c cookies 'http://pan.baidu.com/api/list' | json_pp
$ curl -b cookies -c cookies 'http://pan.baidu.com/api/list?dir=/test' | json_pp

建立目录:

$ curl -b cookies -c cookies 'http://pan.baidu.com/api/create' -F path=/测试curl -F isdir=1 -F size= -F block_list='[]' -F method=post

参考资料

Category: 网络 | Tags: 百度 curl 网页
3
25
2014
0

火狐开发者工具、Object.watch 以及地址栏图标

之前通过 userChrome.js 脚本把火狐的地址栏图标弄回来了,但是一直有这么个问题:打开一个会自动跳转的标签页,比如 t.co 或者登陆状态下的 Google 搜索结果时,首先会显示跳转前的网站的图标(这没问题),然后开始载入新页面了(这问题也不大),然后新页面是使用 HTTPS 的(比如 MDN),于是就会看到地址栏图标后边已经变成绿色、显示出了站点信息(如「Mozilla Foundation (US)」),但是图标仍旧是跳转前的图标

经过探索,我发现火狐在载入新页面时并不会及时更改网站的图标(即该 tab 对象的.image属性;大概是因为默认情况下,即标签页里那个图标,它会被「载入中」的图标取代),但是其它信息都会及时更改,这才造成了不一致。既然这段时间取不到网站的图标,那么用默认图标好了。但问题是,怎么知道地址栏的 URL 已经改变了呢?

我试过在urlbar上监听change事件。Firebug 在调试chrome://browser/content/browser.xul页面时说会有这么个事件,但是真正在 chrome 里运行时却发现没有这样的事件被触发。

还想到了通过 Object.watch 方法要监听tab.linkedBrowser.currentURI属性的更改,但是好像也没有被调用。也许那个 nsIURI 对象一直在那里,只是它的值在变而已。

期间还试过 addProgressListener。大概是用法不对,无果。

后来结合了以上两个方案,决定监听urlbar.value的更改。这下终于好用了。有点小坑的是,因为一直在尝试各种方案,没仔细看文档,结果没注意注册到.watch方法中的函数要返回实际要设置的属性值,把火狐地址栏搞坏掉了……

在调试过程中,浏览器控制台(取代了以前的「错误控制台」,快捷键还是Ctrl-Shift-J)比「代码片断速记器」好用(它实际上是火狐自带调试工具中的「Web 控制台」运行在 chrome 上的版本):

火狐的「浏览器控制台」

  • chrome 脚本中的console.log等日志会记录在浏览器控制台,并且可以按关键字过滤(比如我今天一直在用的userChrome
  • 浏览器控制台支持 Tab 补全
  • 代码片断速记器每次打开之后需要手动切换「在浏览器环境中执行」
  • 代码片断速记器中不支持选择即复制、中键点击即粘贴功能,不支持 GTK 自定义的编辑快捷键
  • 浏览器控制台中直接记录对象的话,点击之后会出现和代码片断速记器Ctrl-i快捷键一样的「对象查看器」

不过浏览器控制台不支持将日志中的对象直接在脚本中使用(Firebug 支持)。

哦还有,MozRepl 在开启 chrome 调试的时候,是连接到最后一个有焦点的火狐窗口的(而不一定是chrome://browser/content/browser.xul这个)。期间搞错了一次,发现里边竟然连gBrowser变量都没有 -_-|||

最后,辛辛苦苦接着出来的新版代码(其实没改多少):

if(location == "chrome://browser/content/browser.xul"){

  var eTLDService = Components.classes["@mozilla.org/network/effective-tld-service;1"]
                    .getService(Components.interfaces.nsIEffectiveTLDService);

  function updateIcon(event){
    var tab = event.target;
    if(tab != gBrowser.selectedTab){
      return;
    }
    var icon = tab.image || 'chrome://mozapps/skin/places/defaultFavicon.png';
    var identity = document.getElementById('identity-box');
    if('chromeUI'.indexOf(identity.className) != -1){
      // already has a pretty icon as list-style-image
      icon = '';
    }
    console.log('updateIcon', icon, tab.linkedBrowser.currentURI.spec, event);
    document.getElementById('page-proxy-favicon').src = icon;
    if('verifiedDomain'.indexOf(identity.className) != -1){
      var identityLabel = document.getElementById('identity-icon-labels');
      identityLabel.collapsed = false;

      var domain;
      try{
        domain = eTLDService.getBaseDomain(tab.linkedBrowser.currentURI);
      }catch(ex){ // NS_ERROR_HOST_IS_IP_ADDRESS
        domain = tab.linkedBrowser.currentURI.host;
      }

      document.getElementById('identity-icon-label').value = domain;
    }
  }

  var container = gBrowser.tabContainer;
  // includes TabSelect (.selected is modified)
  container.addEventListener("TabAttrModified", updateIcon, false);
  document.getElementById('urlbar').watch('value', function(prop, oldval, newval){
    console.log('urlbar value changed to ' + newval);
    document.getElementById('page-proxy-favicon').src = 'chrome://mozapps/skin/places/defaultFavicon.png';
    return newval;
  }, false);

  })();
}
Category: 火狐 | Tags: 火狐 userChrome
3
14
2014
4

Linux 系统时间变更通知

每一次,系统从挂起状态恢复,系统日志里总会多这么几行:

systemd[1]: Time has been changed
crond[324]: time disparity of 698 minutes detected

一个来自 systemd,一个来自 dcron,都是说系统时间改变了。那么它们是怎么知道系统时间改变的呢?

dcron 的代码很少,所以很快就可以找到。因为 dcron 每一次的睡眠时长它自己知道,所以当它再次从睡眠状态醒来,发现时间变化特别大时,它就会察觉到。也就是说,小的变化它会察觉不到的。

systemd 呢?这家伙一直在使用 Linux 新加特性,比如上次发现的 prctl 的 PR_SET_CHILD_SUBREAPER 功能。这次它也没有让我失望,它使用了 timerfd 的一个鲜为人知的标志位——TFD_TIMER_CANCEL_ON_SET。timerfd 是 Linux 2.6.25 引入的特性,而TFD_TIMER_CANCEL_ON_SET这个标志位则据说 Linux 3.0 引入的,但是到目前为止(man-pages 3.61),手册里没有提到它,系统头文件里也没有它。

这个标志位是干什么的呢?其实很简单,是当系统时钟被重设时向程序发送通知,包括通过系统调用设置系统时间,以及系统从硬件时钟更新时间时。当事件发生时,在该 timerfd 上的读取操作会返回 -1 表示失败,而 errno 被设置成ECANCELED。下边是一个简单的演示程序,在系统时间变化时打印一条消息:

#include<unistd.h>
#include<sys/timerfd.h>
#include<stdbool.h>
#include<stdint.h>
#include<errno.h>
#include<stdlib.h>
#include<stdio.h>
#define TIME_T_MAX (time_t)((1UL << ((sizeof(time_t) << 3) - 1)) - 1)
#ifndef TFD_TIMER_CANCEL_ON_SET
#  define TFD_TIMER_CANCEL_ON_SET (1 << 1)
#endif

int main(int argc, char **argv){
  int fd;
  struct itimerspec its = {
    .it_value.tv_sec = TIME_T_MAX,
  };
  fd = timerfd_create(CLOCK_REALTIME, TFD_CLOEXEC);
  if(fd < 0){
    perror("timerfd_create");
    exit(1);
  }
  if(timerfd_settime(fd, TFD_TIMER_ABSTIME|TFD_TIMER_CANCEL_ON_SET,
        &its, NULL) < 0) {
    perror("timerfd_settime");
    exit(1);
  }
  uint64_t exp;
  ssize_t s;
  while(true){
    s = read(fd, &exp, sizeof(uint64_t));
    if(s == -1 && errno == ECANCELED){
      printf("time changed.\n");
    }else{
      printf("meow? s=%zd, exp=%lu\n", s, exp);
    }
  }
  return 0;
}

编译并运行该程序,然后拿 date 命令设置时间试试吧 =w= 当然记得用虚拟机哦,因为系统时间乱掉的时候会发生不好的事情喵~

date 091508002012
Category: Linux | Tags: systemd linux

Mastodon | Theme: Aeros 2.0 by TheBuckmaker.com