PHP數組的實現與操作

| Comments

存儲結構

和其它變量一樣,PHP的數組也是一個zval。存儲數據的哈希表存放在zval->value->ht中。

符號表操作

為實現可轉換成整數的字符串鍵與整數鍵指向同一個元素,在哈希表操作的基礎上封裝了一層,對可轉換成整數的字符串鍵轉換成整數,然後調用zend_hash_index_*操作,否則調用zend_hash_*操作。這就是符號表操作。

用ZEND_HANDLE_NUMERIC處理整數字符串鍵:

1
2
3
4
5
6
static inline int zend_symtable_find(
    HashTable *ht, const char *arKey, uint nKeyLength, void **pData
) {
    ZEND_HANDLE_NUMERIC(arKey, nKeyLength, zend_hash_index_find(ht, idx, pData));
    return zend_hash_find(ht, arKey, nKeyLength, pData);
}

其它符號表操作函數:

1
2
3
4
5
6
7
8
static inline int zend_symtable_exists(HashTable *ht, const char *arKey, uint nKeyLength);
static inline int zend_symtable_del(HashTable *ht, const char *arKey, uint nKeyLength);
static inline int zend_symtable_update(
    HashTable *ht, const char *arKey, uint nKeyLength, void *pData, uint nDataSize, void **pDest
);
static inline int zend_symtable_update_current_key_ex(
    HashTable *ht, const char *arKey, uint nKeyLength, int mode, HashPosition *pos
);

數組操作

初始化

1
2
3
4
5
6
7
8
9
// 初始化數組
zval *zv1;
array_init(zv1);

// 初始化數組並指定哈希表nTableSize的值
array_init_size(zv1, 100);

// 在函數中返回數組:把返回值初始化為數組
array_init(return_value);

插入和更新

1
2
3
4
5
6
7
8
/* Insert at next index */
int add_next_index_*(zval *arg, ...);
/* Insert at specific index */
int add_index_*(zval *arg, ulong idx, ...);
/* Insert at specific key */
int add_assoc_*(zval *arg, const char *key, ...);
/* Insert at specific key of length key_len (for binary safety) */
int add_assoc_*_ex(zval *arg, const char *key, uint key_len, ...);

星號表示類型名,可用類型名如下:

Type Additional arguments
null none
bool int b
long long n
double double d
string const char *str, int duplicate
stringl const char *str, uint length, int duplicate
resource int r
zval zval *value

字符串長度的處理

上述操作對字符串鍵和字符串值的長度的要求不同。_ex函數要求傳入字符串鍵的長度,此長度包含NUL字節。_stringl函數要求傳入字符串值的長度,此長度不包含NUL字節。

栗子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
PHP_FUNCTION(donie_get_arr)
{
  array_init(return_value);

  // add an integer to the given position
  add_index_long(return_value, 1, 2015);

  // append a string to the array
  add_next_index_string(return_value, "dummy string", 1);

  // add a boolean value to the given key
  add_assoc_bool(return_value, "rightOrWrong", 0);

  // take care of string lengths
  add_assoc_stringl_ex(return_value, "keyStringL\0", sizeof("keyStringL\0")-1, "valueEx\0", sizeof("valueEx\0"), 1);

  // store an object in the array
  zval *obj;
  MAKE_STD_ZVAL(obj);
  object_init(obj);
  add_next_index_zval(return_value, obj);
}

PHP哈希表的實現與操作

| Comments

結構

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// 哈希表結構
typedef struct _hashtable {
    uint nTableSize;
    uint nTableMask;
    uint nNumOfElements;           // 全部元素數
    ulong nNextFreeElement;        // 下一個可用的整數鍵
    Bucket *pInternalPointer;      // 枚舉操作時使用,指向當前Bucket
    Bucket *pListHead;
    Bucket *pListTail;
    Bucket **arBuckets;
    dtor_func_t pDestructor;       // 元素的析構函數
    zend_bool persistent;          // 是否在本次請求結束後保留哈希表
    unsigned char nApplyCount;     // 循環級別,防止循環引用導致遍歷哈希表時死循環
    zend_bool bApplyProtection;    // 是否防止死循環
#if ZEND_DEBUG
    int inconsistent;
#endif
} HashTable;

// Bucket結構
typedef struct bucket {
    ulong h;
    uint nKeyLength;
    void *pData;
    void *pDataPtr;
    struct bucket *pListNext;
    struct bucket *pListLast;
    struct bucket *pNext;
    struct bucket *pLast;
    char *arKey;
} Bucket;

哈希衝突處理

哈希表通過計算鍵值的哈希值,將對應的數據映射到對應的槽上。理論上會存在不同的鍵的哈希值相同的情況。

處理哈希衝突的方法一般有兩種:開放尋址和鏈表。開放尋址法是將衝突的元素順序放到下一個空槽,理論上會導致衝突越來越多,性能快速下降。鏈表法是將衝突的元素插入對應的槽,與前一個元素組成一個鏈表。PHP使用鏈表法。

PHP的哈希表中的Buckets組成兩種雙向鏈表。一種由每個槽中的所有Bucket分別組成,一種是整個哈希表中的Bucket組成一個。Bucket結構裡,pNext指向該槽的鏈表中的下一個Bucket,pLast指向上一個;pListNext指向整個哈希表鏈表的下一個Bucket,pListLast指向上一個。

pData與pDataPtr

賦值到Bucket時,數據會被複製一份,pData中保存指向該數據拷貝的指針。特別地,如果保存一個指針到Bucket,會先將該指針保存到pDataPtr,然後將pData指向pDataPtr,即pData中保存的是指向pDataPtr中保存的指針的指針。這樣可以避免一次拷貝數據時分配內存的操作,提高效率。

nTableSize與nTableMask

nTableSize保存的是arBuckets的Bucket個數。它的值永遠是個大於等於8的、2的n次方的整數。當現有容量不滿足需要時,arBuckets會重新分配一個大小是原來兩倍的空間,nTableSize相應地被更新為新的數值。

nTableMask = nTableSize - 1

哈希值h一般比nTableSize大,所以要用哈希值對nTableSize取模,以確定對應的Bucket。由於取模操作運算量大,且nTableSize永遠是2的n次冪,所以用“h & (nTableSize - 1)”替代。

初始化與銷毀

1
2
3
4
5
6
7
8
9
10
11
12
13
// init hashtable
HashTable *myht;
ALLOC_HASHTABLE(myht);
if (zend_hash_init(myht, 100, NULL, NULL, 0) == FAILURE)
{
    FREE_HASHTABLE(myht);
    return FAILURE;
}

// destroy hashtable
zend_hash_destroy(myht);
FREE_HASHTABLE(myht);
return SUCCESS;

初始化哈希表

ALLOC_HASHTABLE就是用emalloc()分配內存。

zend_hash_init()

zend_hash_init(HashTable *ht, uint nSize, dtor_func_t pDestructor, zend_bool persistent ZEND_FILE_LINE_DC)

nSize是哈希表的初始長度,實際分配為最接近指定值的2的n次方,最小為8。

pDestructor是被存儲數據的析構函數,默認為ZVAL_PTR_DTOR,對於一般情況(Bucket中存儲的是zval)適用。

persistent,1表示本次請求結束後保留哈希表,0反之。

銷毀哈希表

zend_hash_clean()對HT所有Bucket調用析構函數,並重置HT的所有指針。

zend_hash_destroy()除了銷毀所有Bucket存儲的數據,連arBuckets的空間也釋放掉。

FREE_HASHTABLE宏其實就是efree()。

操作數字鍵

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
// add a string with an integer key 2 to myht
zval *zv1;
MAKE_STD_ZVAL(zv1);
ZVAL_STRING(zv1, "Hello HT !", 1);
zend_hash_index_update(myht, 2, &zv1, sizeof(zval *), NULL);

// get the next free key
php_printf("The next free key will be %ld.\n", zend_hash_next_free_element(myht));

// append an integer to myht
zval *zv2;
MAKE_STD_ZVAL(zv2);
ZVAL_LONG(zv2, 2015);
if (zend_hash_next_index_insert(myht, &zv2, sizeof(zval *), NULL) == FAILURE)
{
    php_printf("HashTable appendation failed.\n");
}
else
{
    php_printf("HashTable appendation succeeded.\n");
}

// get the size
php_printf("HashTable has a size of %d.\n", zend_hash_num_elements(myht));

// check if an integer key exists
int idx = 3;
if (zend_hash_index_exists(myht, idx))
{
    php_printf("HashTable has an index of the value %ld.\n", idx);
}
else
{
    php_printf("HashTable does not have an index of the value %ld.\n", idx);
}

// get a value by its key
zval **zval_dest;
if (zend_hash_index_find(myht, idx, (void **) &zval_dest) == SUCCESS)
{
    php_printf("The value indexed by %ld is %Z.\n", idx, *zval_dest);
}
else
{
    php_printf("The value indexed by %ld does not exist.\n", idx);
}

// delete the specified value from myht
if (zend_hash_index_del(myht, idx) == FAILURE)
{
    php_printf("The value indexed by %ld failed to be deleted.\n", idx);
}
else
{
    php_printf("The value indexed by %ld is deleted.\n", idx);
}

操作字符串鍵

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
// add an integer indexed by a string key, using zend_hash_update()
zval *zv3;
MAKE_STD_ZVAL(zv3);
ZVAL_LONG(zv3, 1985);
zend_hash_update(myht, "year", sizeof("year"), &zv3, sizeof(zval *), NULL);
php_printf("An integer is updated to the hash-table indexed by a string key.\n");

// add a string indexed by a string key, using zend_hash_add()
zval *zv4;
MAKE_STD_ZVAL(zv4);
ZVAL_STRING(zv4, "Great Donie !", 1);
if (zend_hash_add(myht, "motto", sizeof("motto"), &zv4, sizeof(zval *), NULL) == FAILURE)
{
  php_printf("Cannot add a string indexed by a string key to the hash-table, may be the index already exists.\n");
}
else
{
  php_printf("A string is added to the hash-table indexed by a string key.\n");
}

// get the next free key
php_printf("The next free key will be %ld.\n", zend_hash_next_free_element(myht));

// check if a string key exists
char *key1 = "year";
if (zend_hash_exists(myht, key1, strlen(key1)+1))
{
  php_printf("The key %s exists.\n", key1);
}
else
{
  php_printf("The key %s does not exist.\n", key1);
}

// get the value indexed by a string key
zval **zv_dest2;
if (zend_hash_find(myht, key1, strlen(key1)+1, (void **) &zv_dest2) == SUCCESS)
{
  php_printf("The value indexed by %s is %Z.\n", key1, *zv_dest2);
}
else
{
  php_printf("Failed fetching the value indexed by %s.\n", key1);
}

// delete the value indexed by a string key
if (zend_hash_del(myht, key1, strlen(key1)+1) == SUCCESS)
{
  php_printf("The value indexed by %s is deleted.\n", key1);
}
else
{
  php_printf("The value indexed by %s failed to be deleted.\n", key1);
}

鍵的長度

鍵長包括鍵字符串末尾的NUL字節。如果直接指定,應是sizeof(“key1”);如果是char*類型變量,應是strlen(key1)+1

快速操作

適用於頻繁操作特定鍵的場景,只計算一次哈希值,加速操作。

對應的,有一組名帶“quick”的函數。

1
2
3
4
5
6
7
8
9
// quick operations leveraging a one-time hashed value
zval *zv5;
MAKE_STD_ZVAL(zv5);
ZVAL_STRING(zv5, "Great Donie Leigh !", 1);

ulong h;
h = zend_get_hash_value("motto", sizeof("motto"));
zend_hash_quick_update(myht, "motto", sizeof("motto"), h, &zv5, sizeof(zval *), NULL);
php_printf("The value indexed by motto is updated with the quick operation.\n");

遍歷

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
static int hashtable_traverse_callback(void *pDest TSRMLS_DC, int num_args, va_list args, zend_hash_key *hash_key)
{
  zval **zv = (zval **) pDest;
  char *arg1 = va_arg(args, char*);

  php_printf("The first argument is %s.\n", arg1);

  if (hash_key->nKeyLength == 0)
  {
      php_printf("K-V: %d=>%Z\n", hash_key->h, *zv);
  }
  else
  {
      php_printf("K-V: %s=>%Z\n", hash_key->arKey, *zv);
  }

  return ZEND_HASH_APPLY_KEEP;
}

// traverse the hash table.
zend_hash_apply_with_arguments(myht, hashtable_traverse_callback, 1, "nonsense");

三個函數

遍歷哈希表的三個函數:

1
2
3
4
5
6
7
void zend_hash_apply(HashTable *ht, apply_func_t apply_func TSRMLS_DC);
void zend_hash_apply_with_argument(
    HashTable *ht, apply_func_arg_t apply_func, void *argument TSRMLS_DC
);
void zend_hash_apply_with_arguments(
    HashTable *ht TSRMLS_DC, apply_func_args_t apply_func, int num_args, ...
);

三個函數接受的回調函數的類型:

1
2
3
4
5
typedef int (*apply_func_t)(void *pDest TSRMLS_DC);
typedef int (*apply_func_arg_t)(void *pDest, void *argument TSRMLS_DC);
typedef int (*apply_func_args_t)(
    void *pDest TSRMLS_DC, int num_args, va_list args, zend_hash_key *hash_key
);

zend_hash_key的定義為:

1
2
3
4
5
typedef struct _zend_hash_key {
    const char *arKey;
    uint nKeyLength;
    ulong h;
} zend_hash_key;

nKeyLength為0表示索引是整數,值為h;否則是字符串,值為arKey。

回調函數的返回值:

  • ZEND_HASH_APPLY_KEEP:繼續遍歷。
  • ZEND_HASH_APPLY_REMOVE:遍歷後刪除遍歷過的元素。
  • ZEND_HASH_APPLY_STOP:遍歷當前元素後停止。
  • ZEND_HASH_APPLY_REMOVE | ZEND_HASH_APPLY_STOP:遍歷當前元素後,刪除該元素並停止。

枚舉

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// iterating the hash table
php_printf("Begin iterating the hash table:\n");
HashPosition pos;
zval **data;
char *str_idx;
uint str_len;
ulong num_idx;
for (zend_hash_internal_pointer_reset_ex(myht, &pos);
  zend_hash_get_current_data_ex(myht, (void **) &data, &pos) == SUCCESS;
  zend_hash_move_forward_ex(myht, &pos)
) {
  switch (zend_hash_get_current_key_ex(myht, &str_idx, &str_len, &num_idx, 0, &pos)) {
      case HASH_KEY_IS_LONG:
          php_printf("K-V: %d=>%Z\n", num_idx, *data);
          break;
      case HASH_KEY_IS_STRING:
          php_printf("K-V: %s=>%Z\n", str_idx, *data);
          break;
  }
}

三個函數

三個函數均帶“_ex”後綴,使用外部指針。不帶此後綴的函數使用哈希表內部指針,此時嵌套地遍歷哈希表可能導致指針修改錯誤。

取鍵的新方式

PHP 5.5以上版本新增函數,直接取鍵值到zval:

1
2
3
zval *key;
MAKE_STD_ZVAL(key);
zend_hash_get_current_key_zval_ex(myht, key, &pos);

複製與合併

1
zend_hash_copy(ht_target, ht_source, (copy_ctor_func_t) zval_add_ref, NULL, sizeof(zval *));

zval_add_ref是適用於zval的回調函數,直接引用原數據。

當目標哈希表已存在對應鍵值的數據時,目標元素會被源元素覆蓋。使用zend_hash_merge()可通過最後一個參數指定是否用源數據覆蓋目標數據。

函數zend_hash_merge_ex()可指定一個回調函數,用於過濾要合併的元素:

1
2
3
4
zend_hash_merge_ex(
    Z_ARRVAL_P(return_value), Z_ARRVAL_P(array2), (copy_ctor_func_t) zval_add_ref,
    sizeof(zval *), (merge_checker_func_t) merge_greater, NULL
);

回調函數的格式為:

1
2
3
typedef zend_bool (*merge_checker_func_t)(
    HashTable *target_ht, void *source_data, zend_hash_key *hash_key, void *pParam
);

比較、排序和極值

比較函數:

1
2
3
4
5
6
int zend_hash_compare(
    HashTable *ht1, HashTable *ht2, compare_func_t compar, zend_bool ordered TSRMLS_DC
);

// 回調函數:
typedef int (*compare_func_t)(const void *left, const void *right TSRMLS_DC);

排序函數:

1
2
3
4
5
6
7
int zend_hash_sort(HashTable *ht, sort_func_t sort_func, compare_func_t compar, int renumber TSRMLS_DC);

// 回調函數
typedef void (*sort_func_t)(
    void *buckets, size_t num_of_buckets, register size_t size_of_bucket,
    compare_func_t compare_func TSRMLS_DC
);

極值函數:

1
2
3
int zend_hash_minmax(
    const HashTable *ht, compare_func_t compar, int flag, void **pData TSRMLS_DC
);

flag=0,極小值寫入pData;flag=1,極大值寫入pData。

自動重映射鍵盤

| Comments

每次鍵盤拔出再插入時,鍵盤映射都會失效,要重新執行映射,而且要對不同的鍵盤應用不同的映射方案。試過直接添加udev規則,即使指定X Display和Xauthority也不成功。所以用pyudev寫個腳本(最新版本):

udev.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
#!/usr/bin/env python2
# encoding: utf-8

"""
File:        udev.py
Description: udev monitor script.
Author:      Donie Leigh
Email:       donie.leigh at gmail.com
"""

import glib, os, time
from pyudev import Context, Monitor

PID_FILE = "/tmp/udev_monitor.pid"

def remap_pokerii(device):
    """ Do keyboard remapping when PokerII is plugged in.
    """
    if device.get('ID_VENDOR_ID') == '0f39' \
            and device.action == 'add':
        time.sleep(1)
        os.system('setxkbmap')
        os.system('xmodmap ~/.Xmodmap')

def remap_filco(device):
    """ Do keyboard remapping when Filco is plugged in.
    """
    if device.get('ID_VENDOR_ID') == '04d9' \
            and device.action == 'add':
        time.sleep(1)
        os.system('setxkbmap')
        os.system('xmodmap ~/.Xmodmap')

def is_pid_running(pid):
    """ Check if the given pid is running.
    :pid: int
    :returns: bool
    """
    try:
        os.kill(pid, 0)
    except OSError:
        return False
    return True

def write_pid_or_die():
    """ Write the current pid into pid file or exists if there is already a instance running.
    :returns: void
    """
    if os.path.isfile(PID_FILE):
        pid = int(open(PID_FILE).read())
        if is_pid_running(pid):
            print("Process {0} is still running.".format(pid))
            raise SystemExit
        else:
            os.remove(PID_FILE)

    open(PID_FILE, 'w').write(str(os.getpid()))

def main():
    try:
        from pyudev.glib import MonitorObserver
        def device_event(observer, device):
            remap_pokerii(device)
            remap_filco(device)
    except:
        from pyudev.glib import GUDevMonitorObserver as MonitorObserver
        def device_event(observer, action, device):
            remap_pokerii(device)
            remap_filco(device)

    context = Context()
    monitor = Monitor.from_netlink(context)

    monitor.filter_by(subsystem='usb')
    observer = MonitorObserver(monitor)

    observer.connect('device-event', device_event)
    monitor.start()

    glib.MainLoop().run()

if __name__ == '__main__':
    write_pid_or_die()
    try:
        main()
    except KeyboardInterrupt:
        print("Game over.")

有個坑,監測到鍵盤插入事件後要等一秒再應用映射,否則不成功。

這裡只用了設備的Vendor ID,可以直接用lsusb看。看更多的設備屬性的命令如下:

1
2
3
4
5
6
7
8
# 監視設備變動
udevadm monitor --environment --udev

# 查看設備屬性
udevadm info -a -n [device name]

# 查看文件所屬設備的屬性
udevadm info -a -p [file name]

用樹苺派做家庭監控

| Comments

用樹苺派做視頻監控,當視野內有物體移動時,自動拍照、錄視頻、同步到遠程主機,並提醒到遠程電腦和手機。

用Motion做視頻監控

安裝Motion,修改幾項必要的配置:

/etc/motion/motion.conf
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 照片和視頻存儲路徑
target_dir = /media/sda1/cam

# 允許局域網內其它主機訪問視頻
webcam_localhost off

# 監測到移動物體時,創建作為標識的臨時文件
on_event_start "echo 1 > /tmp/invasion_detected"

# 移動物體消失時,移除臨時文件
on_event_end "rm /tmp/invasion_detected"

# 監測到移動物體並在保存第一張照片時,發送提醒到電腦和手機
on_picture_save [ -f /tmp/invasion_detected ] && [ `cat /tmp/invasion_detected` -gt 0 ] && echo 0 > /tmp/invasion_detected && proxychains /root/SmartHome/script/alert.py -f %f

用Lsyncd同步到VPS

安裝lsyncd並配置:

/etc/lsyncd.conf
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
settings{
    pidfile = "/var/log/lsyncd/lsyncd.pid",
    logfile = "/var/log/lsyncd/lsyncd.log",
    statusFile = "/var/log/lsyncd/lsyncd-status.log",
    statusInterval = 1,
    maxDelays = 1,
    -- nodaemon = true,
}

sync{
    default.rsyncssh,
    source = "/media/sda1/cam",
    host = "myvps.com",
    targetdir = "/opt/cam",
    exclude={ ".*", "*.tmp" },
    rsync = {
        compress = false,
        _extra = {"--bwlimit=50000"},
    }
}

用PushBullet通知電腦和手機

Python有幾個封裝好PushBullet API的模塊,pushbullet.py在被Motion執行的時候報IOError,pushybullet的文件上傳有問題,所以程序裡用yapbl。

1
git clone https://github.com/xbot/SmartHome.git

修改alert.py,填上自己的PushBullet API Key。

訪問PushBullet的API需要科學上網,在Motion的on_picture_save裡用proxychains執行PushBullet腳本。

用樹苺派搭NAS

| Comments

為了讓盒子能直接播放遠程下載的電影,繼續在樹苺派上搭NAS。

安裝samba,然後配置:

/etc/samba/smb.conf
1
2
3
4
5
6
7
[nas]
path = /media/sda1
valid users = @users
force group = users
create mask = 0660
directory mask = 0771
read only = no

把Linux用戶添加到samba並設置密碼:

1
smbpasswd -a pi

盒子上的Kodi硬解有問題,用ES+MX Player替代。電腦上用Kodi。

再次調整磁盤分區

| Comments

自從上次調整磁盤分區,一直把根目錄和主目錄分別掛在一個物理分區下,即使系統掛了或者換發行版也不影響主目錄。最近根分區很緊張,乾脆把兩個分區合併了。

先用UNetBootin安裝Puppy Linux到U盤,需要手工修改U盤裡的syslinux.cfg,把“pmedia=cd”改成“pmedia=usbflash”,然後用U盤啟動。

把主目錄的內容完整複製到移動硬盤:

1
2
3
4
5
6
7
8
9
10
11
12
13
# 掛載主目錄
mkdir /mnt/oldhome
mount -t ext4 /dev/sda2 /mnt/oldhome

# 掛載移動硬盤
mkdir /mnt/bakdisk
mount -t ext4 /dev/sdc1 /mnt/bakdisk

# 複製主目錄
cp -a /mnt/oldhome /mnt/bakdisk/

# 取消掛載主目錄
umount /mnt/oldhome

用gparted刪除主目錄分區,合併到根分區。然後恢復主目錄:

1
2
3
4
5
6
7
8
9
10
11
12
13
# 掛載根分區
mkdir /mnt/newroot
mount -t ext4 /dev/sda1 /mnt/newroot

# 恢復主目錄
cp -a /mnt/bakdisk/* /mnt/newroot/

# 修改fstab,取消主目錄的掛載
vim /mnt/newroot/etc/fstab

# 取消掛載
umount /mnt/bakdisk
umount /mnt/newroot

這段時間讀的幾本書

| Comments

每個危樓聽雨的暮年都有過軟紅十丈的青春。文字凄豔,明清小品裏的上乘之作。後兩記文風模仿前文,意境遠遜,應是僞作,可以不讀。

上部是政治史,下部是文化史。文化史部分很好,漲知識,應該買本經常翻翻。

大部分是講稿,内容不系統,不适合入門。暢銷書不可信啊。

科普書,不知道豆瓣上那麼高的評分哪兒來的。

用樹苺派實現遠程下載

| Comments

遠程用樹苺派利用空閒時間下載大文件,需要百度雲、aria2和VPS。因為網絡運營商給的IP不是真的公網IP,而且免費的動態域名服務不穩定,所以用VPS把樹苺派上的端口轉發到外網。

樹苺派

在樹苺派上部署aria2下載服務,並發佈到VPS。

安裝aria2,創建以下配置文件,修改/media/sda1為實際下載目錄:

/etc/aria2/aria2.conf
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
dir=/media/sda1
file-allocation=prealloc
continue=true
log-level=info
#log-level=debug
max-connection-per-server=10
summary-interval=120
daemon=true
enable-rpc=true
rpc-listen-port=6800
rpc-listen-all=true
max-concurrent-downloads=3
save-session=/etc/aria2/save-session.list
input-file=/etc/aria2/save-session.list
log=/media/sda1/aria.log
disable-ipv6=true
disk-cache=25M
timeout=600
retry-wait=30
max-tries=0
user-agent=netdisk;4.4.0.6;PC;PC-Windows;6.2.9200;WindowsBaiduYunGuanJia

我的樹苺派用Archlinux,創建systemd的服務配置文件:

/etc/systemd/system/aria2c.service
1
2
3
4
5
6
7
8
9
10
11
12
13
[Unit]
Description=aria2c -- file download manager
After=network.target

[Service]
Type=forking
User=%i
WorkingDirectory=%h
Environment=VAR=/var/%i
ExecStart=/usr/bin/aria2c --daemon --enable-rpc --rpc-listen-all --rpc-allow-origin-all -c -D --conf-path=/etc/aria2/aria2.conf

[Install]
WantedBy=multi-user.target

激活並啟動aria2服務。

在NGINX的WWW目錄下安裝aria2的Web界面:

1
git clone https://github.com/binux/yaaw.git

發佈本地服務到VPS:

1
2
3
4
5
# 發佈NGINX
autossh -M 5122 -R 80:localhost:80 myvps.com

# 發佈aria2
autossh -M 5124 -R 6800:localhost:6800 myvps.com

autossh用於保持SSH連接,需要VPS上啟動TCP Echo服務。

VPS

在VPS上啟用TCP Echo服務,安裝xinetd並修改配置文件:

/etc/xinet.d/echo-stream
1
2
3
4
5
6
7
8
9
10
11
12
13
service echo
{
        disable         = no
        id              = echo-stream
        type            = INTERNAL
        wait            = no
        socket_type     = stream
        user                    = root
        server                  = /usr/bin/cat
        log_on_failure          += USERID
        flags                   = REUSE
        only_from               = 127.0.0.1
}

遠程主機

在遠程主機上配置瀏覽器的代理規則,對http://localhost/yaawhttp://localhost:6800/jsonrpc兩個URL使用VPS上的VPN或Shadowsocks代理。

安裝Chrome擴展,然後到百度雲盤裡設置aria2的RPC地址為“http://localhost:6800/jsonrpc”即可。

運行時可裝卸的Mixin的PHP實現

| Comments

PHP的Trait可以實現加載時(load time)的混入(mixin)。作爲元編程的一部分,運行時(run time)的混入擁有更大的靈活性。下面利用PHP的魔術方法實現運行時的混入。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
<?php
/**
 * 支持混入的類
 */
class Component
{
    // ...

    // 所有混入的實例
    private $_behaviors = [];

    /**
     * 魔術方法
     * @param string $name 方法名
     * @param array $arguments 參數數組
     * @return mixed
     * @throws MethodNotFoundException
     */
    public function __call($name, $arguments) {
        foreach ($this->_behaviors as $behavior) {
            if (method_exists($behavior, $name)) {
                return call_user_func_array([$behavior, $name], $arguments);
            }
        }
        throw new MethodNotFoundException(get_class($this), $name);
    }

    /**
     * 魔術方法,從混入對象實例中取屬性值
     * @param string $attrName 屬性名
     * @return mixed
     * @throws AttrNotFoundException
     */
    public function __get($attrName)
    {
        foreach ($this->_behaviors as $behavior) {
            if (property_exists($behavior, $attrName)) {
                return $behavior->$attrName;
            }
        }
        throw new AttributeNotFoundException(get_class($this), $attrName);
    }

    /**
     * 附加混入對象實例
     * @param object $behavior 混入對象實例
     * @param string $name 混入對象實例名稱
     * @return void
     */
    public function attachBehavior($behavior, $name='') {
        if (empty($name))
            $this->_behaviors[] = $behavior;
        else
            $this->_behaviors[$name] = $behavior;
    }

    /**
     * 卸載混入對象實例
     * @param string $name 混入對象實例名稱
     * @return void
     */
    public function detachBehavior($name) {
        unset($this->_behaviors[$name]);
    }

    // ...
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?php
/**
 * 混入類
 */
class Behavior
{
    // ...

    /**
     * 将本實例混入指定對象
     * @param object $object 支持混入的實例
     * @param string $name 目标對象存儲本混入對象實例的鍵值
     * @return void
     * @throws BehaviorNotAttachableException
     */
    public function mixin($object, $name='') {
        if (method_exists($object, 'attachBehavior')) {
            return call_user_func_array([$object, 'attachBehavior'], [$this, $name]);
        }
        throw new BehaviorNotAttachableException(get_class($object));
    }

    // ...
}

使用示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<?php
include_once 'component.php';
include_once 'behavior.php';

class TestBehavior extends Behavior
{
    public function test($what)
    {
        echo "say $what";
    }
}

$c = new Component();
$b = new TestBehavior();

$c->attachBehavior($b, 'test');
echo '<pre>'; var_dump($c); echo '</pre>';

$c->detachBehavior('test');
echo '<pre>'; var_dump($c); echo '</pre>';

$b->mixin($c, 'test');
echo '<pre>'; var_dump($c); echo '</pre>';

$c->detachBehavior('test');
$b->mixin($c);
echo '<pre>'; var_dump($c); echo '</pre>';

$c->test('hello world');

讀完《湘行散記》

| Comments

能讓我這個不喜歡讀散文的人堅持讀完的,是枕著一葉扁舟、聽著潺潺流水、忖著多年後的自己——身在何方、心念何人、情系何物……