上一篇在这里 7z文件格式及其源码的分析(五)
对于流式处理来讲, 主要问题是要处理好文件头的问题. 因为是流式的, 所以,不可能走回头路. 也就不可能说压缩完了之后回到开头来修改头的信息.
7z文件的文件头其实是分为两部分的, 这两部分文件头在前面的几篇分析中都有介绍:
头Header, 就是写在文件前面的 Header. 这个头比较小, 主要记录了文件的版本信息, 以及尾header的位置偏移和校验和等等. 其主要作用是文件类型识别和查找到尾Header
尾Header, 就是写在文件末尾的 Header. 这里记录了 7z 文件的全部压缩信息, 解压主要就靠这里面的信息了.
7z 常见的压缩过程是: 先把 头Header 的一些位置保留, 然后压缩数据. 压缩完之后, 尾Header 写完之后, 再把 尾Header 的大小,偏移,校验和写回到 头Header 中去.
在流式环境下, 最后一步回头写 头Header 的过程显然不可能完成. 因为已经流过了.
7z 的文档里,并没有明确的回答这个问题. 我们还是让它的代码回答吧.
我们找到 7z 的解压代码, 7zIn.cpp 文件. 找到 HRESULT CInArchive::ReadDatabase2() 函数. 大概在 1136 行.
这个函数就是在读取 头Header 信息, 并查找 尾Header 位置.
我们截取它开头的一部分代码片段:
HRESULT CInArchive::ReadDatabase2(
DECL_EXTERNAL_CODECS_LOC_VARS
CArchiveDatabaseEx &db
#ifndef _NO_CRYPTO
, ICryptoGetTextPassword *getTextPassword, bool &passwordIsDefined
#endif
)
{
db.Clear();
db.ArchiveInfo.StartPosition = _arhiveBeginStreamPosition;
db.ArchiveInfo.Version.Major = _header[6];
db.ArchiveInfo.Version.Minor = _header[7];
if (db.ArchiveInfo.Version.Major != kMajorVersion)
ThrowUnsupportedVersion();
UInt32 crcFromArchive = Get32(_header + 8);
UInt64 nextHeaderOffset = Get64(_header + 0xC);
UInt64 nextHeaderSize = Get64(_header + 0x14);
UInt32 nextHeaderCRC = Get32(_header + 0x1C);
UInt32 crc = CrcCalc(_header + 0xC, 20);
#ifdef FORMAT_7Z_RECOVERY
if (crcFromArchive == 0 && nextHeaderOffset == 0 && nextHeaderSize == 0 && nextHeaderCRC == 0)
{
UInt64 cur, cur2;
RINOK(_stream->Seek(0, STREAM_SEEK_CUR, &cur));
const int kCheckSize = 500;
Byte buf[kCheckSize];
RINOK(_stream->Seek(0, STREAM_SEEK_END, &cur2));
int checkSize = kCheckSize;
if (cur2 - cur < kCheckSize)
checkSize = (int)(cur2 - cur);
RINOK(_stream->Seek(-checkSize, STREAM_SEEK_END, &cur2));
RINOK(ReadStream_FALSE(_stream, buf, (size_t)checkSize));
int i;
for (i = (int)checkSize - 2; i >= 0; i--)
if (buf[i] == 0x17 && buf[i + 1] == 0x6 || buf[i] == 0x01 && buf[i + 1] == 0x04)
break;
if (i < 0)
return S_FALSE;
nextHeaderSize = checkSize - i;
nextHeaderOffset = cur2 - cur + i;
nextHeaderCRC = CrcCalc(buf + i, (size_t)nextHeaderSize);
RINOK(_stream->Seek(cur, STREAM_SEEK_SET, NULL));
}
else
#endif
{
if (crc != crcFromArchive)
ThrowIncorrect();
}
db.ArchiveInfo.StartPositionAfterHeader = _arhiveBeginStreamPosition + kHeaderSize;
(...)
}
可以看到, 下面这几句是在从 头Header 上读取 尾Header 信息.
UInt32 crcFromArchive = Get32(_header + 8);
UInt64 nextHeaderOffset = Get64(_header + 0xC);
UInt64 nextHeaderSize = Get64(_header + 0x14);
UInt32 nextHeaderCRC = Get32(_header + 0x1C);
UInt32 crc = CrcCalc(_header + 0xC, 20);
紧接着, 后面又有一句判断:
if (crcFromArchive == 0 && nextHeaderOffset == 0 && nextHeaderSize == 0 && nextHeaderCRC == 0)
如果这几个都读不到怎么办.
看代码, 接下来, 它会从文件末尾开始, 倒着向前查找特征字符:
if (buf[i] == 0x17 && buf[i + 1] == 0x6 || buf[i] == 0x01 && buf[i + 1] == 0x04)
这就是在找 0x17 0x06 或者 0x01 0x04 这两个特征串.
代码已经很明确了:
尾Header 了.通过查看7z的文档(事实上, 前面讲的 尾Header 的结构的时候也能发现)知道.
0x17 是代表压缩Header的标记 kEncodedHeader. 后面的 0x06 是 packstream 的标记 kPackInfo.0x01 是代表普通Header的标记 kHeader. 而 0x04 是 mainstream 的标记 kMainStreamsInfo.这正是 7z 的两种 尾Header 的格式.
到这里, 可以总结一下了:
7z 流式压缩
压缩时可以把头Header中的 偏移量和 crc 和等 留空.
在解压时, 如果检测到这些留空了, 则会从文件末尾开始查找尾Header特征串.
欢迎大家讨论交流: Neil 的博客
quota 可以为用户或用户组设置磁盘配额. 限制用户或用户组能使用的磁盘大小.
sudo apt-get install quota
/etc/fstab 文件,启用 quotavi /etc/fstab
添加 usrjquota=aquota.user,grpjquota=aquota.group,jqfmt=vfsv0 到需要启用 quota 的分区. 比如 /:
# /etc/fstab: static file system information.
#
# Use 'blkid' to print the universally unique identifier for a
# device; this may be used with UUID= as a more robust way to name devices
# that works even if disks are added and removed. See fstab(5).
#
#
# / was on /dev/sda2 during installation
UUID=7cf8dc2b-2541-4684-8931-844b6bd01e9c / ext4 errors=remount-ro,usrjquota=aquota.user,grpjquota=aquota.group,jqfmt=vfsv0 0 1
# /boot was on /dev/sda1 during installation
UUID=4541b27d-84d6-4f70-9077-7cb9308634b6 /boot ext4 defaults 0 2
# swap was on /dev/sda3 during installation
UUID=ccae84a6-7423-4e60-9448-0cf05a053fbc none swap sw 0 0
</code></pre>
3. 重新挂载分区:
touch /aquota.user /aquota.group
chmod 600 /aquota.*
mount -o remount /
4. 启用 quota :
quotacheck -avugmc
quotaon -avug
参考:
http://www.howtoforge.com/how-to-set-up-journaled-quota-on-debian-lenny
http://manpages.ubuntu.com/manpages/hardy/zh_CN/man1/quota.1.html
我们在编写shell的时候, 可能需要修改一些配置文件, 比如需要修改mysql的配置文件, 在里面添加一些设置,修改一些或者删除一些.
怎么在shell中来编辑 文本文件呢.
在交互式环境下, 使用vi就可以了. 手动去修改. 但是在无人值守的shell中呢?
sed 命令了.sed 的意思是: stream editor, 就是流式编辑器.跟很多命令行工具(比如awk)一样, 他的功能及其强大, 当然用法和参数也及其复杂. 这里以极简的方式记录几种用法, 常用的编辑功能基本都能满足了.
sed 处理的基本方式它的基本命令格式大概是这个样子的:
sed -options /patterns file
它的意思是要处理 文件 file, 并且把处理结果直接回显到屏幕上. 所以通常需要配合重定向来保存处理结果.
它通常的工作样式是这样的:
sed -options /patterns file > saved.txt
当然, 可以不用重定向, 而直接指定保存文件名. 这里用到
-i参数.
比如:
sed -i saved.txt -options /patterns file
这样在-i后面跟上一个文件名,就可以了.
如果-i后面没有跟文件名, 那么, 编辑结果将会保存到原文件.
sed -i -options /patters file
这样, 就是直接修改原文件了.
因为我通常都是想直接修改原文件的, 所以下面我都加上-i参数了.
sed 的基本编辑操作.替换 是我们最常用的操作.
他的命令样式为:
sed -i "s/aaaa/bbbb" file
这个命令就是把file中的所有字符串aaaa替换成bbbb, 并保存到原文件中(因为有-i 参数).
命令为:
sed -i "/aaa/i bbbb" file
这个命令是在aaa的前面插入一行bbbb, 注意中间的那个字母 i, 表示insert.
命令为:
sed -i "/aaa/a bbbb" file
这个命令是在aaa的后面插入一样bbbb, 注意中间那个字母a, 表示append.
我们在编写shell脚本的时候,经常需要和 mysql 交互.
如果是交互环境,可能使用这样的命令登录到mysql
#mysql –uroot –p
然后按提示输入密码, 登录. 如果在脚本中, 我们就不得不把密码写在 –p 参数后面. 这样 很容易暴露密码.
幸好mysql提供的有解决方案. 在 “~/.my.cnf” 文件中保存密码就行了.
MySQL官方文档
文件内容大概如下:
[client]
password="MySQL密码"
user=MySQL用户名
其中user 行可以省略, 默认使用当前的用户名填充mysql的登录用户名
再次使用 mysql 命令的时候,就无需输入用户名和密码了,可以自动登录.
还可以给 mysql 命令使用 --defaults-file 参数来指定特定的配置文件路径:
mysql --defaults-file=/folder1/folder2/filename -u 用户名
实现了免密码登录之后, 在脚本中就可以直接使用 -e 参数来执行sql脚本了, 而不用像交互式一样登录到mysql之后执行了.
mysql -e "CREATE DATABASE test"
到这里, 基本上就可以实现完全的无值守 mysql脚本操作了.
参考: http://yzs.me/2142.html
事情的起因是这样的, 因为某些原因, 最近在写 Nodejs 的 c++ module, 然后在js这边调用。 网络通信自然离不开ssl, 于是需要链接到Openssl的库。
我们本来的期望是,需要用户安装有Openssl的运行库, 然后我们的c++ module 动态链接到Openssl的so库上来运行。
起初一切看起来还不错,直到我们发现这个openssl的函数不能工作:
PKCS7_sign()
我们发现:
- 如果我们的 c++ 模块与Openssl库动态链接的话, 编译都没问题. 但是运行会出现: PKCS7_sign 符号无法找到的错误.
- 如果我们的 c++ 模块与Openssl库静态链接的话, 编译也没问题, 但是运行时,调用这个函数的地方没有效果, 这个函数返回值是 0. 按照文档表示出现错误, 但是用 Openssl的函数
ERR_get_error获取错误码也是0. 表示没有错误码.
在linux上是这样, 那在Mac上呢? 用Mac试了一下, 发现Mac没有问题. 于是,想到这可能是Nodejs的一个bug. 然后就去 Nodejs 给它报了一个bug: [https://github.com/joyent/node/issues/8026][1]
同时, google上搜索了 nodejs linking to openssl 类似的关键字.
找到这样几篇文章:
https://github.com/TooTallNate/node-gyp/wiki/Linking-to-OpenSSL
https://github.com/joyent/node/issues/3915
http://serverfault.com/questions/338092/how-can-i-build-node-js-using-static-libssl-and-crypto-libraries
https://github.com/robhawkes/node-extension/issues/1
通过搜索, 我们发现, 原来Nodejs自己也使用了Openssl 库, 推测nodejs自己的crypto模块也是使用Openssl lib实现的. 这点从Nodejs的源码中就能发现, 它包含了最新的Openssl的全部源码.
其中写上面第一篇文章: https://github.com/TooTallNate/node-gyp/wiki/Linking-to-OpenSSL 的那个帅哥是Nodejs的开发人员.
基本结论:
- Nodejs 自己使用了Openssl
- 在Nodejs 0.6之前, Nodejs是动态链接到 Openssl 库的. 而之后的版本都是静态链接的.
这时发现 Node 那边已经回复我的bug了: https://github.com/joyent/node/issues/8026
Node 解释的原因:
Node 自己编译之后, 把自己没用到的符号清除, 所以我们在运行时就找不到符号了. 于是他们把这bug 修掉了. 保留了全部符号. 这导致 Node 的体积大了 400k.
感谢Node的快速回复, 不得不佩服Node的活跃程度. 赞.
最近新旧电脑更替, 需要拷贝文件到新电脑。几百G的文件,局域网拷贝只有3M左右的速度。 找了很多方法都没用。
最后试试给笔记本插上网线吧, 速度果然解决。 12M的拷贝速度,还可以。
看来是无线的速率太低。 虽然我的路由器是300M的速度, 但是实际局域网可能是因为链接有低速的手机设备,所以实际工作在54M的频段上。