字符串 $ date -d@1354118400 Thu Nov 29 00:00:00 CST 2012 或者加上格式控制: $ date -d@ …">

且听风吟

Don't panic! I'm a programmer.

Unix Epoch 与 时间字符串的转换

| Comments

字符串 —> Epoch

$ date -d "2012/11/29" +%s
1354118400

Epoch —> 字符串

$ date -d@1354118400
Thu Nov 29 00:00:00 CST 2012

或者加上格式控制:

$ date -d@1354118400 '+%Y-%m-%d %H:%M:%S'
2012-11-29 00:00:00

Git Tips and Tricks

| Comments

在提交历史中查找指定文本

git log -G'password' --all

or

git rev-list --all | (
while read revision; do
    git grep -F 'password' $revision
done
)

or

for revision in `git rev-list --all`; 
do
    git grep -F 'password' $revision;
done

or git log -S'bad\_code' path/to/file 查看指定代码片段在哪个版本出现过。 git blame path/to/file: 查看指定文件的每一行的最后修改时间是在哪个版本,但不能查看删除的行或者被替换的行 参见man git-blame.

git commit

git commit -c <commit> reedit commit message of git commit -C <commit> reuse commit message of

git底层命令

git ls-tree
git write-tree
git ls-files
git cat-file
git rev-parse

.git/refs 保存所有引用的命名空间 .git/refs/heads 所有的本地分支

git commit —allow-empty —amend

[TODO] strace命令

.git/index 包含文件索引的目录树,记录了文件名和文件的状态信息,文件的内容存储在.git/objects中,文件索引建立了文件和对象库中的文件对象之间的对应关系。

git checkout . / git checkout —
丢弃workspace中的内容,保留暂存区的内容

git checkout HEAD . / git checkout HEAD
用HEAD指向的commit中的内容替代workspace和暂存区的内容,这将导致当前workspace和暂存区的内容被丢弃。

使用git reflog挽回错误的reset

git reflog
git reflog show master

分支的变更记录都会被记录在.git/logs/refs下,通过 git reflog命令可以获得分支的历史变更记录,然后可以通过git reset 命令将HEAD任意重置到任意一个提交。

Java 字符串的==和equals

| Comments

实例

问题起源于这段代码:

if (Environment.getExternalStorageState() == Environment.MEDIA_MOUNTED) {
    Log.d(TAG, "mounted");
} else {
    Log.d(TAG, "umounted");
}

在SD卡处于mounted的状态下,这个case打印出umounted.

问题

现在SD卡处于mounted的状态,所以Environment.getExternalStorageState()方法会返回”mounted”。
跟踪Environment.getExternalStorageState方法的实现,如果SD卡处于mounted的状态,该方法返回Environment.MEDIA_MOUNTED这个字符串常量。 那么问题是,Environment.MEDIA_MOUNTED是一个字符串常量,一个字符串常量在JVM中只有一份,== 符号比较的是两边的对象是否是同一个引用,这样来说这里应该输出”mounted”才对,为什么会输出”umounted”呢?也就是说等号两边的两个字符串不是同一个引用。

原因在于只有在编译期确定的字符串常量才会被放进常量池,这个常量池会被保存在class文件中,稍后被JVM加载。而对于运行时才生成的字符串是不会放入常量池的。Environment.getExternalStorageState()方法返回的字符串虽然是一个字符串常量,但是这个常量在编译期并未确定,所以该方法返回的字符串不是从常量池中取得的。

String.intern()方法用于在运行时将一个字符串加入常量池中,所以对这个例子,Environment.getExternalStorageState().intern() == Environment.MEDIA\_MOUNTED才会返回true。

Reference

C语言字符串定义与初始化

| Comments

字符串的两种定义方式

  1. char数组

    char sa[] = “hello world”;

  2. char指针

    char *sp = “hello world”;

这两种方式都产生了一个”hello world”的字符串常量,字符串常量存储在静态存储区中,静态存储区中的内容在程序运行的整个过程中都存在,而且只存储一份。

数组与指针的关系

在第一种定义中,字符串常量在编译时就已经分配好了空间,但直到运行时才会为数组分配存储空间,这时,存储在静态存储区中的字符串常量会被拷贝一份到数组中,此后,数组名sa与&sa[0]等价。重要的是,sa是一个常量,你不能修改sa的值,你可以通过sa+1来标识数组里的下一个元素,也可以修改数组里的元素。然而,sa++是不允许的,因为自增运算符只能作用于变量而不是常量。

第二种定义中,程序运行时只为指针变量sp分配了存储空间,用来字符串常量”hello world”的地址,这时,字符串常量没有被拷贝。sp的值是可以改变的,如++sp将指向字符e。

总之,数组初始化是从静态存储区中把一个字符串拷贝给数组,而指针初始化只是复制这个字符串的地址。

示例

#include <stdio.h>

int main(int args, char **argv) {
    char sa[] = "hello world";
    char *sp = "hello world";

    // sa++; 错误,sa是常量
    sp++;

    sa[1] = 'M';
    *(sa+2) = 'X';
    // sp[1] = 'M'; // 错误,不能修改常量值

    printf("%s\n", sa);
}

C语言函数指针与指针函数

| Comments

#include <stdio.h>

int bar(int, char*);

// 指针函数
char *echo_hello(char *s) {
    char *result;
    int len = strlen(s) + strlen("hello ") + 1;
    result = (char *)malloc(len * sizeof(char));
    memset(result,0, len * sizeof(char));
    snprintf(result, len, "%s%s", "hello ", s);
    return result;
}

int main(int args, char **argv) {
    // 函数指针
    int  (*foo) (int, char *);

    foo = bar;
    foo = &bar;

    foo(2, "hello world");
    (*foo)(2, "hello world");

    printf("%s\n", echo_hello("programmer!"));
}

int bar(int a, char *s) {
    printf("calling bar, int=%d, string=%s\n", a, s);
}

Reference

http://blog.csdn.net/porscheyin/article/details/3461632/

使用lvm管理磁盘分区

| Comments

最近在给新同学安装Ubuntu系统后发现有的同学home分区空间不够了,于是研究了下利用lvm对home空间进行扩容。
lvm的概念介绍来自这里.

LVM是逻辑盘卷管理(Logical Volume Manager)的简称,它是Linux环境下对磁盘分区进行管理的一种机制,LVM是建立在硬盘和分区之上的一个逻辑层,来提高磁盘分区管理的灵活性。通过LVM系统管理员可以轻松管理磁盘分区,如:将若干个磁盘分区连接为一个整块的卷组(volume group),形成一个存储池。管理员可以在卷组上随意创建逻辑卷组(logical volumes),并进一步在逻辑卷组上创建文件系统。管理员通过LVM可以方便的调整存储卷组的大小,并且可以对磁盘存储按照组的方式进行命名、管理和分配,例如按照使用用途进行定义:“development”和“sales”,而不是使用物理磁盘名“sda”和“sdb”。而且当系统添加了新的磁盘,通过LVM管理员就不必将磁盘的文件移动到新的磁盘上以充分利用新的存储空间,而是直接扩展文件系统跨越磁盘即可。


lvm的基本组成

lvm包括以下几个概念:

  • Physical volume (PV)
    指代磁盘上的物理分区
  • Volume group (VG)
    VG类似与物理硬盘,由多个物理分区组成,可以在VG上创建一个或者多个逻辑卷(LV)
  • Logical volume (LV)
    在VG的基础上划分出来的逻辑分区,在这个分区上可以建立文件系统,如home
  • Physical extent (PE)
    每个逻辑卷被划分的最小可寻址单元,一般为4MB。

接下来记录一下对home分区进行扩容的过程。

准备工作

已有的环境:
/dev/sda5 是一块windows分区,格式为fat32;
/dev/sda9 为当前home所在的分区,格式为ext4.
现在需要把/dev/sda5合并到home分区中。在建立lvm分区之前,需要备份这两块分区中的数据,因为之后的操作会对这两块分区进行格式化。

首先备份两块分区的数据,重启机器进入recovery模式,以root用户登录,卸载home所在的/dev/sda9

# umount /home

安装lvm

# apt-get install lvm2

修改物理分区类型为8e

作为PV的物理分区类型必须为8e,表示这是一块lvm的物理分区。我们使用fdisk对/dev/sda5和/dev/sda9这两个物理分区的分区类型进行修改。

# fdisk /dev/sda

WARNING: DOS-compatible mode is deprecated. It's strongly recommended to
         switch off the mode (command 'c') and change display units to
         sectors (command 'u').

Command (m for help): t
Partition number (1-10): 5
Hex code (type L to list codes): 8e
Changed system type of partition 5 to 8e (Linux LVM)
Command (m for help): t
Partition number (1-10): 9
Hex code (type L to list codes): 8e
Changed system type of partition 9 to 8e (Linux LVM)
Command (m for help): w       //将修改写入磁盘

最后fdisk -l查看一下修改后的分区。

创建物理卷

# pvcreate /dev/sda5
pvcreate -- physical volume "/dev/sda5" successfully created
# pvcreate /dev/sda9
pvcreate -- physical volume "/dev/sda9" successfully created
# pvdisplay

创建卷组

# vgcreate lvm_sda /dev/sda5 // 以/dev/sda5为基础创建一个名为lvm_sda的卷组
# vgextend lvm_sda /dev/sda9 // 将/dev/sda9添加进lvm_sda卷组
# vgdisplay lvm_sda

创建逻辑卷

# lvcreate -L 150G lvm_sda -n lvolhome

这里在lvm_sda上创建了一个150G的名为lvolhome的逻辑卷,这时会生成/dev/lvm_sda/lvolhome设备节点。

创建文件系统

# mkfs.ext4 /dev/lvm_sda/lvolhome

然后将其挂载到/home,并创建对应用户的home目录。

# mount /dev/lvm_sda/lvolhome /home
# cd /home
# mkdir calvin
# chown -R calvin:calvin calvin/

设置开机挂载逻辑卷

# vi /etc/fstab

删除已有的home挂载信息,添加:

/dev/lvm_sda/lvolhome /home ext4 defaults 0 2

重启系统,done。

Reference

setComponentEnabledSetting Doesn’t Work on Widget

| Comments

需求

有一个系统属性disable_widget用来标识是否需要禁用某个widget,如果是,那么禁用某个widget.

实现与问题

PackageManager.setComponentEnabledSetting 可以用来禁用某个组件,包括activity,receiver等等。被禁用的组件会被持久化到/data/system/packages.xml中,如:

<package name="com.android.setupwizard" codePath="/system/app/SetupWizard.apk" nativeLibraryPath="/data/data/com.android.setupwizard/lib" flags="1" ft="13349457a90" it="13349457a90" ut="13349457a90" version="130" userId="10016">
  <sigs count="1">
  <cert index="0" />
  </sigs>
  <disabled-components>
  <item name="com.android.setupwizard.SetupWizardActivity" />
  </disabled-components>
</package>

因为widget实际上就是个reveiver,它接收android.appwidget.action.APPWIDGET_UPDATE的action,所以开始的思路是:
创建一个BroadcastReceiver,接收Intent.ACTION_BOOT_COMPLETED这个动作,从而在启动完成后调用SystemProperties.get(“disable_widget”),如果需要禁用这个widget,那么调用:

PackageManager.setComponentEnabledSetting(widgetComponentName,PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
                PackageManager.DONT_KILL_APP);

但是问题是,调用这个方法disable掉这个widget后,发现必须把设备重启之后才能生效…
经过一番挖掘,发现问题出在com.android.server.AppWidgetService.java。

开机后,SystemServer会调用AppWidgetService的systemReady()方法,这个方法通过PackageManager查询所有的widget receiver组件,保存到mInstalledProviders变量列表中,并持久化widget信息到/data/system/appwidgets.xml中。 而在Launcher上长按添加widget时的那个widget列表信息也是通过AppWidgetService取得mInstalledProviders列表。

问题在于我们通过PackageManager.setComponentEnabledSetting()禁用掉某个widget后,packagemanager确实将这个组件disable了,但是AppWidgetService却没有去从packagemanager reload widget信息,这就导致了mInstalledProviders中保存的widget信息还是开机时load进来的那些信息,并没有与pm进行同步。直到下一次开机调用systemReady重新加载widget信息才会刷新这个列表。

Reference

Multipart/form-data与application/x-www-form-urlencoded

| Comments

在浏览器上提交表单时,根据提交的方式,get或者post,表单数据被封装进url或者http头中,这是http请求的Content-Type字段值默认为application/x-www-form-urlencoded

POST /upload HTTP/1.1
Host: localhost:8080
Origin: http://localhost:8080
User-Agent: Mozilla/5.0 (X11; U; Linux x86_64; en-us) AppleWebKit/531.2+ (KHTML, like Gecko) Version/5.0 Safari/531.2+
Accept: application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5
Referer: http://localhost:8080/
Content-Type: application/x-www-form-urlencoded
Accept-Encoding: gzip
Cookie: JSESSIONID=18DE3AD950262E1EDA9A453A8C3BF430
Content-Length: 14

username=scott

HTTP/1.1 
200 OK
Server: Apache-Coyote/1.1
Content-Length: 0
Date: Wed, 06 Jun 2012 06:31:20 GMT

这种情况下在服务器端使用request.getParameter("username")就可以取出scott这个值。

当需要通过表单上传文件时,这时enctype必须为multipart/form-data或者multipart/mixed。比如:

<form method="post" action="upload"
    enctype="multipart/form-data">
        <input type="file" name="upload_file"/>
        <input type="text" name="username"/>
        <input type="submit" value="Submit" />
</form>

这时浏览器会以表单为单位将所有提交数据进行分割,并分隔符boundary分隔:

POST /upload HTTP/1.1
Host: localhost:8080
Origin: http://localhost:8080
User-Agent: Mozilla/5.0 (X11; U; Linux x86_64; en-us) AppleWebKit/531.2+ (KHTML, like Gecko) Version/5.0 Safari/531.2+
Content-Type: multipart/form-data; boundary=----WebKitFormBoundarysJagkbbAojjjPOh8
Accept: application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5
Referer: http://localhost:8080/
Accept-Encoding: gzip
Cookie: JSESSIONID=18DE3AD950262E1EDA9A453A8C3BF430
Content-Length: 33076

------WebKitFormBoundarysJagkbbAojjjPOh8
Content-Disposition: form-data; name="upload_file"; filename="mmssms.db-shm"
Content-Type: application/octet-stream
.....
.....
------WebKitFormBoundarysJagkbbAojjjPOh8
Content-Disposition: form-data; name="username"

scott
------WebKitFormBoundarysJagkbbAojjjPOh8--
HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Content-Length: 343
Date: Wed, 06 Jun 2012 06:47:49 GMT

这时在server端就不能用request.getParameter这种方式获取表单数据了,必须对request的数据进行解码。这部分工作已经有现成的库帮我们做了,比如Apache Common Fileupload

Reference

http://zh.wikipedia.org/wiki/MIME

拉风的vim保存方法

| Comments

用vim编辑root用户的文件时,经常忘了敲sudo,结果保存不了。一个work around是保存为一个临时文件,然后sudo cp回去,不过从现在开始,我们可以这样做,输入:

:w !sudo tee %

%代表当前编辑文件的文件路径,tee %表示把stdin的输入输入到stdout,同时保存到一个文件。
w !{cmd}意思是说执行一个外部命令cmd,同时把当前缓冲区的内从通过管道连接到cmd的stdin。

改善SD卡读取速度

| Comments

一年前刷了CM的rom,发现sd卡速度下降了很多,今天看见有人发了一个kernel的patch改善这个问题,原理是修改sys文件系统下的一个read_ahead_kb参数值,这个值指定了每次从SD卡上读取数据时预读的大小。

adb shell 'echo 128 > /sys/devices/virtual/bdi/179:0/read_ahead_kb'

但这个值也不是越大越好,128是个比较平衡的值,因为在读取小文件的情况下,如果这个值太大,那么预读的数据很大几率是无用的数据,反而降低性能,这个帖子有详细的分析。

这个值的定义在/include/linux/mm.h中:

/* readahead.c */
#define VM_MAX_READAHEAD    128 /* kbytes */
#define VM_MIN_READAHEAD    16  /* kbytes (includes current page) */

设置开机生效

注意:重启后这个设置就失效了,为了避免每次开机后都要设置,可以在init.rc脚本中加上:

write /sys/devices/virtual/bdi/179:0/read_ahead_kb 128

或者利用init.d脚本的支持,在/system/etc/init.d目录下创建一个文件10sdcard:

#!/system/bin/sh
echo 128 > /sys/devices/virtual/bdi/179:0/read_ahead_kb

这样每次开机后都会执行这段脚本。

CM支持init.d开机脚本的方法

  1. 在init.rc的class_start default上加上

    # Run sysinit
    exec /system/bin/sysinit
    
    class_start default
    
  2. 建立文件/system/bin/sysinit

    #!/system/bin/sh
    
    export PATH=/sbin:/system/sbin:/system/bin:/system/xbin
    /system/bin/logwrapper /system/xbin/run-parts /system/etc/init.d
    

然后将启动脚本放在/system/etc/init.d目录下,这些脚本以数字命名,run-parts命令按照顺序排序依次执行这些脚本(cron命令也是利用run-parts命令执行指定目录下的脚本的,参见/etc/crontab)。

其实也可以这样做:
在init.rc的class_start default上加入

start sysinit

然后在所有service定义的后面加上:

service sysinit /system/bin/logwrapper /system/xbin/busybox run-parts /system/etc/init.d
    disabled
    oneshot

Reference