coolEx

Today will be better

redis误同步恢复

redis主备同步非常方便,通过slaveof命令即可同步。上周应用切换了redis,想让新的redis和旧的redis进行数据同步,结果把新redis中其他应用已经写入的所有数据都删除了。。。这里记录下恢复方法。

首先先恢复redis的dump文件。还好现在阿里云弹性计算集群给每个镜像每天都进行了备份,通过操作前一天的备份镜像,快照制作当时的整个redis目录复制了出来,作为恢复的基础。

然后将恢复回来的文件再复制一份备份,原先那份修改redis的配置文件,修改启动端口(我从默认的6379改成了6479),然后启动旧的redis,成功将当时的dump文件载入到新的redis实例中。

查询到redis有migrate命令,能够将key迁移到另外一个redis实例中(具体参考这里)。通过bash命令,循环的将备份的实例所有key,都试图通过migrate命令迁移到新的redis中。

迁移过程中,迁移成功的key,在旧的实例中会被删除,失败的key,可以看见失败原因都是key is busy。也就是说,要迁移的key在新的实例中已经存在了。

和使用方确定了所有已经存在的key,是hash类型的,都是要保留的数据。因此,通过一个简单的shell脚本,读取备份实例中hash类型key,并添加到新的redis中。bash脚本为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#!/bin/bash
OLD_REDIS="./redis-cli -p 6479"
NEW_REDIS="./redis-cli"

KEYS=`$OLD_REDIS keys '*'`

for k in $KEYS
do
        key_type=`$OLD_REDIS type $k`
        if [ $key_type = "hash" ]; then
                hash_keys=`$OLD_REDIS HKEYS $k`
                for hash_key in $hash_keys
                do
                        hash_value=`$OLD_REDIS HGET $k $hash_key`
                        $NEW_REDIS HSET $k $hash_key $hash_value
                        echo "merge $k $hash_key to new redis"
                done
                #eval "$OLD_REDIS DEL $k"
        fi
done

逻辑非常简单,就是先通过keys命令获取到所有迁移失败的key,然后通过type命令获取类型。如果是hash类型的key,遍历所有的hash key,然后读取出一个key下所有的键值对,并通过hset命令放到新的redis实例中。最后将合并完成的key从备份实例中删除。这里由于业务上只需要合并hash类型的,其他容器类型(list, set等)也可以通过类似的方式做新老合并。

chrome插件远程加载js

chrome插件将js直接注入页面有两种方式,一种是通过Manifest文件中指定js文件,一种是通过chrome.tab.executeScript方式注入。具体可以参考这个官方文档

由于各种需求,需要将部分js从后端服务器中进行加载。所以只能通过tab.executeScript的方式。具体函数的定义,可以参考官方文档。大致就是能够给定一个文件或者一段代码,在指定的tab中运行。

首先,这个函数必须在background中执行,页面中的content-script本身,是没有权限调用chrome.tab api的。所以,如果页面中的js要执行,需要通过chrome插件的通信机制(port, message),通知background才能运行。

其次,运行的目标tab,可以指定,也可以传null,如果目标tab为null,则表示需要执行的tab是“当前tab”。所以如果使用了null,则在注入前千万不能换tab,否则就注入到其他tab中去了。

然后,参数InjectDetails中可以指定是执行code,还是file。如果是file,可以是本地文件(插件中打包的文件),也可以是远程文件。特别注意,通过file执行远程文件,有着严格的约束(参照这里):‘Currently, we allow whitelisting origins with the following schemes: HTTPS, chrome-extension, and chrome-extension-resource.’也就是说,只能执行插件内部打包的文件,或者基于https的远程地址。其他还有例外的就是给开发者用的localhost了。我们要加载的资源,不可能放在本地,也不可能打包到插件中,也没有办法去搞到https的证书。所以不能通过执行file的方式加载远程js。

要解决这个问题,只能通过执行code的方式。由于我们在manifest文件中配置了background拥有跨域请求资源的权限,我们可以直接在background中,远程通过xhr的方式获取js内容,然后进行code的执行。这样就能绕开之前遇到的无法执行http协议的js文件的问题。大致的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function(){
    if (xhr.readyState == 4 && xhr.status == 200) {
        var code = xhr.responseText;
        chrome.tabs.executeScript(null, {code: code, allFrames: true}, function(){
            if(typeof callback === 'function') {
                callback();
            }
        });
    }
}
var ts = new Date().getTime();
var u;
if(url.indexOf('?') === -1){
    u = url + '?_t=' + ts;
} else {
    u = url + '&_t=' + ts;
}
xhr.open('GET',u,true);
xhr.send(null);

最后,还有一个小问题,就是InjectDetails中的allFrames参数。这个参数大致的作用,就是如果为true,js会在页面的所有frame中进行执行,否则就只在页面的top frame中进行执行。由于frame之间的document会互相隔离,所有如果要让frame之间能够通信,就需要设置成true,把代码注入到所有的frame中去。

boost spirit的简单使用

尝试通过cpp-netlib来做http服务器,但是这个库只能简单的解析http结构,像cookie等结构,都要自己解析,了解到spirit可以通过类似bnf范式格式定义字符串格式并解析。

boost本身有个类似的例子,解析的是通过分号或者&符号分割的键值对字符串,并放到对应的map中去。具体代码可以参照这里。所以基于这个代码,简单的进行修改之后,就能解析http cookie了。

首先,http cookie的格式,定义在rfc6265上。这里定义了服务器发送给浏览器的Set-Cookie头格式,和浏览器发给服务器的Cookie头的BNF范式。这里定义的太复杂,解析的时候没有考虑到这么多字符(特别是排除一些控制字符),大致的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
namespace parser
{
    namespace qi = boost::spirit::qi;
    typedef std::map<std::string, std::string> pairs_type;
   
    template <typename Iterator>
    struct cooke_sequence : qi::grammar<Iterator, pairs_type()> {
        qi::rule<Iterator, pairs_type()> query;
        qi::rule<Iterator, std::pair<std::string, std::string>()> pair;
        qi::rule<Iterator, std::string()> key, value;
       
        cooke_sequence() : cooke_sequence::base_type(query) {
            query =  pair >> *(qi::lit(';') >> pair);
            pair  =  *qi::lit(' ') >> key >> *qi::lit(' ') >> -('=' >> *qi::lit(' ') >> value >> *qi::lit(' '));
            key   =  qi::char_("a-zA-Z_%") >> *qi::char_("a-zA-Z_0-9%");
            value = +(qi::char_ - ';');
        }
    };
}

这里简化了key和value,特别是value,只要是非分号的,都能解析到value中。使用也非常简单:

1
2
3
4
5
6
7
8
namespace qi = boost::spirit::qi;
parser::cooke_sequence<std::string::const_iterator> p;
parser::pairs_type value;
if(qi::parse(c.begin(), c.end(), p, value)) {
    for(auto &entry : value) {
        cookies.insert(std::make_pair(entry.first, entry.second));
    }
}

直接实例化cooe_sequence,将cookie字符串传入,就可以解析成map,然后再放入到自己的结构体中。这里直接抄了示例中的代码,所以直接使用了map,一般cookie没必要排序,可以直接使用unordered_map,通过hash表存放。

minidlna支持共享rmvb文件

之前将盒子作为下载机,为了能够直接播放上面的电影和电视,就通过minidlna,将视频共享出来,这样能够支持通过nexus 10平板播放这些电影。但是,即使升级到了1.1.0版本,minidlna还是不能将设置的视频目录中的rmvb文件共享出来。

参照网上搜索到的让 minidlna 支持 rmvb、gb2312 mp3 标签这篇文章,根据里面的patch,修改了1.1.0版本的minidlna之后,可以在平板上看见rmvb文件了。

由于前文提到的补丁,是针对1.0版本的minidlna,参照补丁修改的位置,自己对补丁进行了修改,重新写了一个ebuild文件之后,搞定了。新的补丁文件:

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
diff -ur minidlna-1.1.0/metadata.c minidlna-1.1.0.new/metadata.c
--- minidlna-1.1.0/metadata.c   2013-03-09 08:03:03.000000000 +0800
+++ minidlna-1.1.0.new/metadata.c       2013-09-07 20:19:31.700278182 +0800
@@ -903,6 +903,8 @@
                        xasprintf(&m.mime, "video/x-matroska");
                else if( strcmp(ctx->iformat->name, "flv") == 0 )
                        xasprintf(&m.mime, "video/x-flv");
+               else if( strcmp(ctx->iformat->name, "rm") == 0 )
+                       asprintf(&m.mime, "video/x-pn-realvideo");
                if( m.mime )
                        goto video_no_dlna;
 
diff -ur minidlna-1.1.0/upnpglobalvars.h minidlna-1.1.0.new/upnpglobalvars.h
--- minidlna-1.1.0/upnpglobalvars.h     2013-04-05 07:39:12.000000000 +0800
+++ minidlna-1.1.0.new/upnpglobalvars.h 2013-09-07 20:21:40.564283420 +0800
@@ -168,7 +168,8 @@
        "http-get:*:audio/mp4:*," \
        "http-get:*:audio/x-wav:*," \
        "http-get:*:audio/x-flac:*," \
-       "http-get:*:application/ogg:*"
+       "http-get:*:application/ogg:*,"\
+       "http-get:*:video/x-pn-realvideo:*"
                                                                                                                                                                                   
 #define DLNA_FLAG_DLNA_V1_5      0x00100000                                                                                                                                        
 #define DLNA_FLAG_HTTP_STALLING  0x00200000                                                                                                                                        
diff -ur minidlna-1.1.0/utils.c minidlna-1.1.0.new/utils.c                                                                                                                          
--- minidlna-1.1.0/utils.c      2013-04-03 07:29:21.000000000 +0800                                                                                                                
+++ minidlna-1.1.0.new/utils.c  2013-09-07 20:18:40.796283001 +0800                                                                                                                
@@ -375,6 +375,7 @@                                                                                                                                                                
                ends_with(file, ".m2t") || ends_with(file, ".mkv")   ||                                                                                                            
                ends_with(file, ".vob") || ends_with(file, ".ts")    ||                                                                                                            
                ends_with(file, ".flv") || ends_with(file, ".xvid")  ||                                                                                                            
+               ends_with(file, ".rm")  || ends_with(file, ".rmvb")  ||                                                                                                            
 #ifdef TIVO_SUPPORT
                ends_with(file, ".TiVo") ||
 #endif

ebuild文件就是在src_prepare阶段,增加了:epatch “${FILESDIR}”/${PN}-1.1.0-rmvb.patch,打上这个补丁即可。

webtail——websocket

前面介绍了服务器端如何监听和增量读取文件,这里通过基于boost asio的websocketpp,实现了一个简单的websocket服务端,能够和浏览器进行通信,将读取到的文件通过websocket协议进行实时传送。

关于websocket的简单介绍,可以参考维基百科。websocket协议,在rfc6455中定义。

websocketpp对websocket和简单的http都进行了比较好的封装,只要实现几个handler,就可以完成对连接、消息等的操作和控制。主要需要处理的,可能有以下的handler:

1
2
3
4
typedef lib::function<void(connection_hdl)> open_handler;
typedef lib::function<void(connection_hdl)> close_handler;
typedef lib::function<void(connection_hdl)> http_handler;
typedef lib::function<void(connection_hdl,message_ptr)> message_handler

分别处理连接创建,连接关闭,http请求和消息请求。其中connection_hdl是连接的weak_ptr。

这里对websocket使用很简单,唯一的需求,就是维护已经建立的连接(既创建连接的时候记录,关闭连接的时候移出),然后通过将自己的回调注册到文件监控类中,实时的将消息推送到websocket客户端。

首先,维护一个set,用来保存当前已经建立的所有连接:

1
typedef std::set<websocketpp::connection_hdl,boost::owner_less<websocketpp::connection_hdl> > ConnectionSet;

然后在建立连接的时候插入到这个set中:

1
2
3
4
5
void WebSocketServer::onOpen ( websocketpp::connection_hdl hdl )
{
    boost::lock_guard<boost::mutex> lock(_mutex);
    _conns.insert(hdl);
}

在连接关闭的时候,移除连接:

1
2
3
4
5
void WebSocketServer::onClose ( websocketpp::connection_hdl hdl )
{
    boost::lock_guard<boost::mutex> lock(_mutex);
    _conns.erase(hdl);
}

另外,定义一个让filewatcher调用的回调:

1
2
3
4
5
6
7
8
void WebSocketServer::write ( const std::string& content )
{
    boost::lock_guard<boost::mutex> lock(_mutex);
    BOOST_AUTO(it, _conns.begin());
    for(; it != _conns.end(); ++it) {
        _s.send(*it, content, websocketpp::frame::opcode::text);
    }
}

这个回调很简单,就是当有读取到内容的时候,遍历连接集合,向每个连接发送具体的内容。

最后,为了方便用户访问,当发现是标准http请求的时候,我们返回一个简单的html,用于显示和建立websocket连接。如果不指定具体的http_handler,websocketpp在发现请求是http的时候,会返回http的426错误(Upgrade Required)。

1
2
3
4
5
6
7
void WebSocketServer::httpHandler ( websocketpp::connection_hdl hdl )
{
    server::connection_ptr connPtr = _s.get_con_from_hdl(hdl);;
   
    connPtr->set_status(websocketpp::http::status_code::ok);
    connPtr->set_body(htmlContent);
}

另外,还有一个小坑。按照websocketpp的example,在启动的时候都是直接调用server的listen函数,而且使用的都是只有端口号的那个实现。实际使用过程中,发现这个只有端口号的实现,直接使用了ipv6协议。虽说如果本机同时支持ipv6和ipv4的情况下,两个协议对应的端口都会监听,但是遇到了服务器上关闭了ipv6,会导致boost asio抛出address_family_not_supported异常,导致应用被迫退出。为了兼容这种方式,对这个异常进行了抓取,重新降级尝试ipv4协议,这样能够很好的在只有ipv4的服务器上进行使用。

1
2
3
4
5
6
7
8
9
try{
    _s.listen(port);
} catch(boost::system::system_error const& e) {
    if(e.code() == boost::asio::error::address_family_not_supported) {
        _s.listen(boost::asio::ip::tcp::v4(), port);
    }
} catch (...) {
    throw;
}

webtail——文件监控

文件监控直接通过了linux的inotify接口实现。这里没有考虑移植性,也就没像tailf那样,通过宏来判断是否支持inotify,如果不支持,降级使用循环轮寻的方式读取。

inotify的使用还是比较方便的基本上就是:inotify_init,inotify_add_watch,然后配合read系统调用,获取文件修改信息。因此实现也非常方便。

首先是在构造函数里面初始化inotify:

1
inotifyFd = inotify_init();

然后提供一个watch接口,通过传入前文描述的TFile对象和内容读取的回调函数,添加对应文件的监控和回调。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void FileWatcher::watch ( boost::shared_ptr< TFile > tFile, std::list< FileWatcher::ReadCallBack > callBackList )
{
    if(!tFile->hasError() && !callBackList.empty()) {
        int wd = inotify_add_watch(inotifyFd, tFile->name().c_str(), IN_MODIFY);
        if(wd > 0) {
            tFileMap.insert(std::make_pair<int, boost::shared_ptr<TFile> >(wd, tFile));
            callBackMap.insert(std::make_pair<int, std::list<ReadCallBack> >(wd, callBackList));
           
            //init read
            std::string initContent = tFile->read();
            BOOST_FOREACH(ReadCallBack &callback, callBackList) {
                callback(initContent);
            }
        }
    }
}

这里通过TFile的文件名,向内核注册添加该文件的modify事件,并且在注册成功之后,进行初始读取(这里有个小问题,由于后面websocket端没有做缓存,所以由于初始读取的时候还没有任何websocket客户端连接,所以通过web无法读取初始内容,也就是文件最后10行)。同时,这个类维护两个hashmap,分别是监听描述符wd->tFile和wd->callbacklist。

监听完成后,就是启动监听,也就是通过读取fd,感知被监听文件的变更,由于这里只监听了文件修改,那么读取到这个事件之后,就可以对该文件进行增量读取(前文已经描述了读取方法)。

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
char * buffer = new char[inotifyBufferSize];
while(!_interrupted) {
    if(read(inotifyFd, buffer, inotifyBufferSize) < 0) {
        if(errno == EINTR) {
            // interrupt
            delete buffer;
            return;
        }
    }
    struct inotify_event *event = ( struct inotify_event * ) buffer;
    int wd = event->wd;
    BOOST_AUTO(tFileIter, tFileMap.find(wd));
    if(tFileIter != tFileMap.end()) {
        boost::shared_ptr<TFile> tFile = tFileIter->second;
        std::string content = tFile->read();
        BOOST_AUTO(iter, callBackMap.find(wd));
        if(iter != callBackMap.end()) {
            std::list<ReadCallBack> callbacks = iter->second;
            BOOST_FOREACH(ReadCallBack &callback, callbacks) {
                callback(content);
            }
        }
    }
}
delete buffer;

这里参照inotify的文档,首先读取缓冲区大小设置为

1
static const int inotifyBufferSize = sizeof(struct inotify_event) + NAME_MAX + 1;

也就是inotify_event结构的长度,和名字最大值。由于inotify_event是变长字段(包含一个可选的文件名字段),所以这里采用了系统限制的文件名最大值NAME_MAX,这个宏在climits中定义,在linux中大小为255字节。
然后通过系统调用read,读取文件描述符inotifyFd,这里如果没有新的事件产生,read会进入阻塞状态,节省系统资源。如果有返回,则处理返回的inotify_event对象(注意在监听modify事件的时候,是没有文件名的)。通过结构中的wd,从之前保存的hashmap中获取对应的tFile对象进行增量读取,然后再读取wd对应的回调函数,将读取内容返回。
这里有个小问题需要处理,就是如何中断读取。之前为了在gtest中能够通过单元测试的方式进行测试,通过查看手册可以知道,如果read调用被系统信号中断,会标记错误码为EINTR。所以,当读取失败的时候,可以通过对ERRNO检查,判断是否是信号中断。

由于程序会一直运行,知道通过信号终止,所以析构变的不是很重要了。这里析构函数里面通过调用inotify_rm_watch将之前保存的wd全部去掉,然后通过close调用交inotify的文件描述符关闭即可:

1
2
3
4
5
6
7
8
9
10
11
FileWatcher::~FileWatcher()
{
    if(inotifyFd > 0) {
        boost::unordered::unordered_map<int, boost::shared_ptr<TFile> >::iterator iter;
        for(iter = tFileMap.begin(); iter != tFileMap.end(); ++iter) {
            inotify_rm_watch(inotifyFd, iter->first);
        }
       
        close(inotifyFd);
    }
}

webtail——文件读取

先介绍下背景,之前遇到过服务器上需要长时间tail一个日志,之前经常是通过一个终端连到服务器上,但是对于长时间观察,就不太方便:老是要开着终端,没法用手机等移动设备查看;多人共享查看比较麻烦,都需要登录到服务器上。

webtail实现类似于tail,能够持续读取一个文件,并将文件内容通过websocket,实时推送到web端。webtail文件读取基于linux的inotify,所以没有可移植性,websocket使用基于asio的websocketpp,代码维护在这里

webtail代码维护在https://gitorious.org/webtail/webtail,目前还是一个简单可用的小应用,还有很多可以提升的地方。

本文介绍下文件读取的部分。

文件读取分为两个部分,文件读取和文件监控。

文件读取:
由于unix的文件多次打开独立维护file table,共享v-node table(参照APUE 3.10 file sharing)。因此只维护一个文件描述符和当前文件偏移,每次读取通过fstat获取文档当前大小,和维护的文件偏移进行对比,如果小于文件大小,则读取文件直到末尾,并输出。
这里有几点是模仿tailf的:

首先是不修改文件访问时间。这是通过在open系统调用中,增加O_NOATIME,按照man open的解释:Do not update the file last access time (st_atime in the inode) when the file is read(2). This flag is intended for use by indexing or backup programs, where its use can significantly reduce the amount of disk activity. This flag may not be effective on all file systems. One example is NFS, where the server main‐tains the access time. 启动了这个参数,通过read调用读取文件的时候,不会修改atime了。

其次是首次读取的时候,读取文件最后10行(由于websocket发送没有做缓存,所以从web端没法看见)。这里代码也是参照了tailf:

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
char *buffer = new char[initLen * BUFSIZ];
char *p = buffer;
char lineBuffer[BUFSIZ];
int readSize;
int lineCount = 0;
bool head = true;
int i = 0, j = 0;
while((readSize = ::read(fd, lineBuffer, BUFSIZ - 1)) > 0) {
    for(i = 0, j = 0; i < readSize; ++i) {
        // read line, save to buffer
        if(lineBuffer[i] == '\n') {
            std::memcpy(p, lineBuffer + j, i - j + 1);
            std::memset(p + i - j + 1, '\0', 1);
            j = i + 1;
            if(++lineCount >= initLen) {
                lineCount = 0;
                head = false;
            }
            p = buffer + (lineCount * BUFSIZ);
        }
    }
    // read break in the middle of line
    if(j < i) {
        // finished read all files
        if(readSize < BUFSIZ) {
            std::memcpy(p, lineBuffer + j, i - j + 1);
            std::memset(p + i - j + 1, '\0', 1);
            ++lineCount;
        } else if (j == 0){     // long line drop?
            continue;
        } else {
            // not finished, seek to line begin
            curPos = lseek(fd, j - i -1, SEEK_CUR);
        }
    }
}

std::string initReadResult;
if(head) {
    for(i = 0; i < lineCount; ++i) {
        initReadResult += (buffer + i * BUFSIZ);
    }
} else {
    for(i = lineCount; i < initLen; ++i) {
        initReadResult += (buffer + i * BUFSIZ);
    }
   
    for(i = 0; i < lineCount; ++i) {
        initReadResult += (buffer + i * BUFSIZ);
    }
}

curPos = lseek(fd, 0, SEEK_CUR);
delete buffer;

首先声明一段用来保存最终n行的缓存,缓存最长行长度是BUFSIZ(这个在linux中的定义是8192字节),然后每次读取BUFSIZ-1个字节(最后一个用来放\0,类似fgets的实现),解析其中的换行符(由于只打算在linux中使用,所以只解析\n)。最后根据状态,从缓存中读取最后n行数据到string中。

如果不是初始读取,之前已经说过逻辑了,将fstat获取到的文件大小和保存的当前偏移做比较,如果有新的内容,则读取,没有就直接返回。

华为c8812E手机恢复

今天早上8812e突然无限重启,拔了电池重启还是一样。最后通过恢复官方包才搞定。恢复方法记录下:
1、下载官方包:在这里可以下载官方固件
2、将下载的zip包解压缩,然后将里面的目录直接复制到tf卡根目录。里面有一个文档,写了具体的步骤
3、然后按住音量上+音量下+电源,手机会自动从这个固件进行恢复
4、恢复完成之后,终于能够启动进入系统了,然后就是root和安装gapps

要完成这两个操作,首先要刷入一个自定义recovery,随便找了一个乐蛙的recovery:
1、进入fastboot,先关闭快速启动,然后按音量下+电源,进入fastboot模式
2、连上电脑,然后通过fastboot flash recovery recovery.img,将recovery刷入
3、直接通过fastboot reboot即可重启手机

root和安装gapp就方便了,直接放到tf卡,然后重启进入recovery(音量上+电源),然后选择对应的zip包安装即可。

mysql innodb数据恢复

这几天在从一台已经启动不了的机器上恢复一个web应用。其中数据库使用的是mysql,部分表采用myisam,部分表采用了innodb。但是innodb没有采用一个表一个数据文件的形式,也就是说,所有的数据都在一个ibdata1文件中。

由于那台机器已经无法启动,通过无盘系统进入之后,将mysql的data目录整体复制出来,在另一台服务器上试图恢复数据。

首先,先将数据库文件复制到新机器的mysql data对应的目录中,然后启动mysql,能看见之前的表,但是无法查询出数据。有一下异常日志:

[ERROR] Cannot find or open table process2/process_risk from
the internal data dictionary of InnoDB though the .frm file for the
table exists. Maybe you have deleted and recreated InnoDB data
files but have forgotten to delete the corresponding .frm files
of InnoDB tables, or you have moved .frm files to another database?
or, the table contains indexes that this version of the engine
doesn’t support.

具体原因就是之前说过的,这些表都采用了一个数据文件,所有数据都在ibdata1文件中。

然后替换原有的ibdata1文件,启动mysql失败,异常日志为:

InnoDB: Error: auto-extending data file /data/mysqldata1/innodb_ts/ibdata1 is of a different size
InnoDB: 3200 pages (rounded down to MB) than specified in the .cnf file:
InnoDB: initial 131072 pages, max 0 (relevant if non-zero) pages!
InnoDB: Could not open or create data files.
InnoDB: If you tried to add new data files, and it failed here,
InnoDB: you should now edit innodb_data_file_path in my.cnf back
InnoDB: to what it was, and remove the new ibdata files InnoDB created
InnoDB: in this failed attempt. InnoDB only wrote those files full of
InnoDB: zeros, but did not yet use them in any way. But be careful: do not
InnoDB: remove old data files which contain your precious data!

因为没有修改数据文件的配置,这里发现数据文件大小校验失败了。
从这里看见,需要恢复的数据文件有3200页,通过查询mysql文档innodb也结构,可以知道现在mysql innodb每个页的大小是固定的,均为16k,这里的3200页,折算下:3200 * 16 / 1024 = 50M
因此,修改mysql的my.cnf为:

1
innodb_data_file_path = ibdata1:50M;ibdata2:50M:autoextend

既使用ibdata1文件,标注大小为50M,同时创建ibdata2文件,初始大小为50M,并且自动扩展。

这时候就可以重启mysql,从日志中可以看见,mysql成功加载了ibdata1,并且自动创建了初始大小为50M的ibdata2文件,然后开始恢复数据。数据恢复完成之后,就可以立即使用mysqldump,将对应的整个database导出成sql文件,方面后面恢复到其他mysql中。

gentoo bash No such file or directory 问题排查

本来打算给龙芯盒子上的gentoo安装一个minidlna,然后当作nas给平板提供视频源,结果手贱同时进行了更新world的操作。结果更新了之后,ssh连接提示bash找不到。

启动到盒子里面的另一个debian系统,发现这些文件本身是存在的。查询emerge.log,发现最后安装失败的是baselayout包,错误是说要merge /lib32目录。

之前遇到过类似的问题,想用ldd查看/bin/bash,但是提示“not a dynamic executable”,直接执行提示No such file or directory。参照这篇文章,使用readelf,直接读bash文件,发现它需要使用/lib32/ld.so.1,但是这个目录被baselayout的安装脚本给干掉了。手工新建一个软连接,将/lib32 -> /lib,系统启动OK了。