分类 技术 下的文章

今天,我喜欢的 meetup 网站上有一篇我超爱的文章!Suchakra Sharma@tuxology 在 twitter/github)的一篇非常棒的关于传统 BPF 和在 Linux 中最新加入的 eBPF 的讨论文章,正是它促使我想去写一个 eBPF 的程序!

这篇文章就是 —— BSD 包过滤器:一个新的用户级包捕获架构

我想在讨论的基础上去写一些笔记,因为,我觉得它超级棒!

开始前,这里有个 幻灯片 和一个 pdf。这个 pdf 非常好,结束的位置有一些链接,在 PDF 中你可以直接点击这个链接。

什么是 BPF?

在 BPF 出现之前,如果你想去做包过滤,你必须拷贝所有的包到用户空间,然后才能去过滤它们(使用 “tap”)。

这样做存在两个问题:

  1. 如果你在用户空间中过滤,意味着你将拷贝所有的包到用户空间,拷贝数据的代价是很昂贵的。
  2. 使用的过滤算法很低效。

问题 #1 的解决方法似乎很明显,就是将过滤逻辑移到内核中。(虽然具体实现的细节并没有明确,我们将在稍后讨论)

但是,为什么过滤算法会很低效?

如果你运行 tcpdump host foo,它实际上运行了一个相当复杂的查询,用下图的这个树来描述它:

评估这个树有点复杂。因此,可以用一种更简单的方式来表示这个树,像这样:

然后,如果你设置 ether.type = IPip.src = foo,你必然明白匹配的包是 host foo,你也不用去检查任何其它的东西了。因此,这个数据结构(它们称为“控制流图” ,或者 “CFG”)是表示你真实希望去执行匹配检查的程序的最佳方法,而不是用前面的树。

为什么 BPF 要工作在内核中

这里的关键点是,包仅仅是个字节的数组。BPF 程序是运行在这些字节的数组之上。它们不允许有循环(loop),但是,它们 可以 有聪明的办法知道 IP 包头(IPv6 和 IPv4 长度是不同的)以及基于它们的长度来找到 TCP 端口:

x = ip_header_length
port = *(packet_start + x + port_offset) 

(看起来不一样,其实它们基本上都相同)。在这个论文/幻灯片上有一个非常详细的虚拟机的描述,因此,我不打算解释它。

当你运行 tcpdump host foo 后,这时发生了什么?就我的理解,应该是如下的过程。

  1. 转换 host foo 为一个高效的 DAG 规则
  2. 转换那个 DAG 规则为 BPF 虚拟机的一个 BPF 程序(BPF 字节码)
  3. 发送 BPF 字节码到 Linux 内核,由 Linux 内核验证它
  4. 编译这个 BPF 字节码程序为一个 原生 native 代码。例如,这是个ARM 上的 JIT 代码 以及 x86 的机器码
  5. 当包进入时,Linux 运行原生代码去决定是否过滤这个包。对于每个需要去处理的包,它通常仅需运行 100 - 200 个 CPU 指令就可以完成,这个速度是非常快的!

现状:eBPF

毕竟 BPF 出现已经有很长的时间了!现在,我们可以拥有一个更加令人激动的东西,它就是 eBPF。我以前听说过 eBPF,但是,我觉得像这样把这些片断拼在一起更好(我在 4 月份的 netdev 上我写了这篇 XDP & eBPF 的文章回复)

关于 eBPF 的一些事实是:

  • eBPF 程序有它们自己的字节码语言,并且从那个字节码语言编译成内核原生代码,就像 BPF 程序一样
  • eBPF 运行在内核中
  • eBPF 程序不能随心所欲的访问内核内存。而是通过内核提供的函数去取得一些受严格限制的所需要的内容的子集
  • 它们 可以 与用户空间的程序通过 BPF 映射进行通讯
  • 这是 Linux 3.18 的 bpf 系统调用

kprobes 和 eBPF

你可以在 Linux 内核中挑选一个函数(任意函数),然后运行一个你写的每次该函数被调用时都运行的程序。这样看起来是不是很神奇。

例如:这里有一个 名为 disksnoop 的 BPF 程序,它的功能是当你开始/完成写入一个块到磁盘时,触发它执行跟踪。下图是它的代码片断:

BPF_HASH(start, struct request *);
void trace_start(struct pt_regs *ctx, struct request *req) {
    // stash start timestamp by request ptr
    u64 ts = bpf_ktime_get_ns();
    start.update(&req, &ts);
}
...
b.attach_kprobe(event="blk_start_request", fn_name="trace_start")
b.attach_kprobe(event="blk_mq_start_request", fn_name="trace_start")

本质上它声明一个 BPF 哈希(它的作用是当请求开始/完成时,这个程序去触发跟踪),一个名为 trace_start 的函数将被编译进 BPF 字节码,然后附加 trace_start 到内核函数 blk_start_request 上。

这里使用的是 bcc 框架,它可以让你写 Python 式的程序去生成 BPF 代码。你可以在 https://github.com/iovisor/bcc 找到它(那里有非常多的示例程序)。

uprobes 和 eBPF

因为我知道可以附加 eBPF 程序到内核函数上,但是,我不知道能否将 eBPF 程序附加到用户空间函数上!那会有更多令人激动的事情。这是 在 Python 中使用一个 eBPF 程序去计数 malloc 调用的示例

附加 eBPF 程序时应该考虑的事情

  • 带 XDP 的网卡(我之前写过关于这方面的文章)
  • tc egress/ingress (在网络栈上)
  • kprobes(任意内核函数)
  • uprobes(很明显,任意用户空间函数??像带调试符号的任意 C 程序)
  • probes 是为 dtrace 构建的名为 “USDT probes” 的探针(像 这些 mysql 探针)。这是一个 使用 dtrace 探针的示例程序
  • JVM
  • 跟踪点
  • seccomp / landlock 安全相关的事情
  • 等等

这个讨论超级棒

在幻灯片里有很多非常好的链接,并且在 iovisor 仓库里有个 LINKS.md。虽然现在已经很晚了,但是我马上要去写我的第一个 eBPF 程序了!


via: https://jvns.ca/blog/2017/06/28/notes-on-bpf---ebpf/

作者:Julia Evans 译者:qhwdw 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

有时候,您可能需要修改多个文件,或要将一个文件的内容复制到另一个文件中。在图形用户界面中,您可以在任何图形文本编辑器(如 gedit)中打开文件,并使用 CTRL + CCTRL + V 复制和粘贴内容。在命令行模式下,您不能使用这种编辑器。不过别担心,只要有 vim 编辑器就有办法。在本教程中,我们将学习使用 vim 编辑器同时编辑多个文件。相信我,很有意思哒。

安装 Vim

Vim 编辑器可在大多数 Linux 发行版的官方软件仓库中找到,所以您可以用默认的软件包管理器来安装它。例如,在 Arch Linux 及其变体上,您可以使用如下命令:

$ sudo pacman -S vim

在 Debian 和 Ubuntu 上:

$ sudo apt-get install vim

在 RHEL 和 CentOS 上:

$ sudo yum install vim

在 Fedora 上:

$ sudo dnf install vim

在 openSUSE 上:

$ sudo zypper install vim

使用 Linux 的 Vim 编辑器同时编辑多个文件

现在让我们谈谈正事,我们可以用两种方法做到这一点。

方法一

有两个文件,即 file1.txtfile2.txt,带有一堆随机单词:

$ cat file1.txt
ostechnix
open source
technology
linux
unix

$ cat file2.txt
line1
line2
line3
line4
line5

现在,让我们同时编辑这两个文件。请运行:

$ vim file1.txt file2.txt

Vim 将按顺序显示文件的内容。首先显示第一个文件的内容,然后显示第二个文件,依此类推。

在文件中切换

要移至下一个文件,请键入:

:n

要返回到前一个文件,请键入:

:N

如果有任何未保存的更改,Vim 将不允许您移动到下一个文件。要保存当前文件中的更改,请键入:

ZZ

请注意,是两个大写字母 ZZSHIFT + zz)。

要放弃更改并移至上一个文件,请键入:

:N!

要查看当前正在编辑的文件,请键入:

:buffers

您将在底部看到加载文件的列表。

要切换到下一个文件,请输入 :buffer,后跟缓冲区编号。例如,要切换到第一个文件,请键入:

:buffer 1

打开其他文件进行编辑

目前我们正在编辑两个文件,即 file1.txtfile2.txt。我想打开另一个名为 file3.txt 的文件进行编辑。

您会怎么做?这很容易。只需键入 :e,然后输入如下所示的文件名即可:

:e file3.txt

现在你可以编辑 file3.txt 了。

要查看当前正在编辑的文件数量,请键入:

:buffers

请注意,对于使用 :e 打开的文件,您无法使用 :n:N 进行切换。要切换到另一个文件,请输入 :buffer,然后输入文件缓冲区编号。

将一个文件的内容复制到另一个文件中

您已经知道了如何同时打开和编辑多个文件。有时,您可能想要将一个文件的内容复制到另一个文件中。这也是可以做到的。切换到您选择的文件,例如,假设您想将 file1.txt 的内容复制到 file2.txt 中:

首先,请切换到 file1.txt

:buffer 1

将光标移动至在想要复制的行的前面,并键入yy 以抽出(复制)该行。然后,移至 file2.txt

:buffer 2

将光标移至要从 file1.txt 粘贴复制行的位置,然后键入 p。例如,您想要将复制的行粘贴到 line2line3 之间,请将鼠标光标置于行前并键入 p

输出示例:

line1
line2
ostechnix
line3
line4
line5

要保存当前文件中所做的更改,请键入:

ZZ

再次提醒,是两个大写字母 ZZ(SHIFT + z)。

保存所有文件的更改并退出 vim 编辑器,键入:

:wq

同样,您可以将任何文件的任何行复制到其他文件中。

将整个文件内容复制到另一个文件中

我们知道如何复制一行,那么整个文件的内容呢?也是可以的。比如说,您要将 file1.txt 的全部内容复制到 file2.txt 中。

先打开 file2.txt

$ vim file2.txt

如果文件已经加载,您可以通过输入以下命令切换到 file2.txt

:buffer 2

将光标移动到您想要粘贴 file1.txt 的内容的位置。我想在 file2.txt 的第 5 行之后粘贴 file1.txt 的内容,所以我将光标移动到第 5 行。然后,键入以下命令并按回车键:

:r file1.txt

这里,r 代表 “read”。

现在您会看到 file1.txt 的内容被粘贴在 file2.txt 的第 5 行之后。

line1
line2
line3
line4
line5
ostechnix
open source
technology
linux
unix

要保存当前文件中的更改,请键入:

ZZ

要保存所有文件的所有更改并退出 vim 编辑器,请输入:

:wq

方法二

另一种同时打开多个文件的方法是使用 -o-O 标志。

要在水平窗口中打开多个文件,请运行:

$ vim -o file1.txt file2.txt

要在窗口之间切换,请按 CTRL-w w(即按 CTRL + w 并再次按 w)。或者,您可以使用以下快捷方式在窗口之间移动:

  • CTRL-w k – 上面的窗口
  • CTRL-w j – 下面的窗口

要在垂直窗口中打开多个文件,请运行:

$ vim -O file1.txt file2.txt file3.txt

要在窗口之间切换,请按 CTRL-w w(即按 CTRL + w 并再次按 w)。或者,使用以下快捷方式在窗口之间移动:

  • CTRL-w l – 左面的窗口
  • CTRL-w h – 右面的窗口

其他的一切都与方法一的描述相同。

例如,要列出当前加载的文件,请运行:

:buffers

在文件之间切换:

:buffer 1

打开其他文件,请键入:

:e file3.txt

将文件的全部内容复制到另一个文件中:

:r file1.txt

方法二的唯一区别是,只要您使用 ZZ 保存对当前文件的更改,文件将自动关闭。然后,您需要依次键入 :wq 来关闭文件。但是,如果您按照方法一进行操作,输入 :wq 时,所有更改将保存在所有文件中,并且所有文件将立即关闭。

有关更多详细信息,请参阅手册页。

$ man vim

建议阅读

您现在掌握了如何在 Linux 中使用 vim 编辑器编辑多个文件。正如您所见,编辑多个文件并不难。Vim 编辑器还有更强大的功能。我们接下来会提供更多关于 Vim 编辑器的内容。

再见!


via: https://www.ostechnix.com/how-to-edit-multiple-files-using-vim-editor/

作者:SK 译者:jessie-pang 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

我其实不想将它分解开给你看,用户应用程序其实就是一个可怜的 瓮中大脑 brain in a vat

它与外部世界的每个交流都要在内核的帮助下通过系统调用才能完成。一个应用程序要想保存一个文件、写到终端、或者打开一个 TCP 连接,内核都要参与。应用程序是被内核高度怀疑的:认为它到处充斥着 bug,甚至是个充满邪恶想法的脑子。

这些系统调用是从一个应用程序到内核的函数调用。出于安全考虑,它们使用了特定的机制,实际上你只是调用了内核的 API。“ 系统调用 system call ”这个术语指的是调用由内核提供的特定功能(比如,系统调用 open())或者是调用途径。你也可以简称为:syscall

这篇文章讲解系统调用,系统调用与调用一个库有何区别,以及在操作系统/应用程序接口上的刺探工具。如果彻底了解了应用程序借助操作系统发生的哪些事情,那么就可以将一个不可能解决的问题转变成一个快速而有趣的难题。

那么,下图是一个运行着的应用程序,一个用户进程:

它有一个私有的 虚拟地址空间—— 它自己的内存沙箱。整个系统都在它的地址空间中(即上面比喻的那个“瓮”),程序的二进制文件加上它所使用的库全部都 被映射到内存中。内核自身也映射为地址空间的一部分。

下面是我们程序 pid 的代码,它通过 getpid(2) 直接获取了其进程 id:

#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>

int main()
{
    pid_t p = getpid();
    printf("%d\n", p);
}

pid.c download

在 Linux 中,一个进程并不是一出生就知道它的 PID。要想知道它的 PID,它必须去询问内核,因此,这个询问请求也是一个系统调用:

它的第一步是开始于调用 C 库的 getpid(),它是系统调用的一个封装。当你调用一些函数时,比如,open(2)read(2) 之类,你是在调用这些封装。其实,对于大多数编程语言在这一块的原生方法,最终都是在 libc 中完成的。

封装为这些基本的操作系统 API 提供了方便,这样可以保持内核的简洁。所有的内核代码运行在特权模式下,有 bug 的内核代码行将会产生致命的后果。能在用户模式下做的任何事情都应该在用户模式中完成。由库来提供友好的方法和想要的参数处理,像 printf(3) 这样。

我们拿一个 web API 进行比较,内核的封装方式可以类比为构建一个尽可能简单的 HTTP 接口去提供服务,然后提供特定语言的库及辅助方法。或者也可能有一些缓存,这就是 libc 的 getpid() 所做的:首次调用时,它真实地去执行了一个系统调用,然后,它缓存了 PID,这样就可以避免后续调用时的系统调用开销。

一旦封装完成,它做的第一件事就是进入了内核 超空间 hyperspace 。这种转换机制因处理器架构设计不同而不同。在 Intel 处理器中,参数和 系统调用号加载到寄存器中的,然后,运行一个 指令 将 CPU 置于 特权模式 中,并立即将控制权转移到内核中的全局系统调用 入口。如果你对这些细节感兴趣,David Drysdale 在 LWN 上有两篇非常好的文章(其一其二)。

内核然后使用这个系统调用号作为进入 sys_call_table 的一个 索引,它是一个函数指针到每个系统调用实现的数组。在这里,调用了 sys_getpid

在 Linux 中,系统调用大多数都实现为架构无关的 C 函数,有时候这样做 很琐碎,但是通过内核优秀的设计,系统调用机制被严格隔离。它们是工作在一般数据结构中的普通代码。嗯,除了完全偏执的参数校验以外。

一旦它们的工作完成,它们就会正常返回,然后,架构特定的代码会接手转回到用户模式,封装将在那里继续做一些后续处理工作。在我们的例子中,getpid(2) 现在缓存了由内核返回的 PID。如果内核返回了一个错误,另外的封装可以去设置全局 errno 变量。这些细节可以让你知道 GNU 是怎么处理的。

如果你想要原生的调用,glibc 提供了 syscall(2) 函数,它可以不通过封装来产生一个系统调用。你也可以通过它来做一个你自己的封装。这对一个 C 库来说,既不神奇,也不特殊。

这种系统调用的设计影响是很深远的。我们从一个非常有用的 strace(1) 开始,这个工具可以用来监视 Linux 进程的系统调用(在 Mac 上,参见 dtruss(1m) 和神奇的 dtrace;在 Windows 中,参见 sysinternals)。这是对 pid 程序的跟踪:

~/code/x86-os$ strace ./pid

execve("./pid", ["./pid"], [/* 20 vars */]) = 0
brk(0)                                  = 0x9aa0000
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
mmap2(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7767000
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat64(3, {st_mode=S_IFREG|0644, st_size=18056, ...}) = 0
mmap2(NULL, 18056, PROT_READ, MAP_PRIVATE, 3, 0) = 0xb7762000
close(3)                                = 0

[...snip...]

getpid()                                = 14678
fstat64(1, {st_mode=S_IFCHR|0600, st_rdev=makedev(136, 1), ...}) = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7766000
write(1, "14678\n", 614678
)                  = 6
exit_group(6)                           = ?

输出的每一行都显示了一个系统调用、它的参数,以及返回值。如果你在一个循环中将 getpid(2) 运行 1000 次,你就会发现始终只有一个 getpid() 系统调用,因为,它的 PID 已经被缓存了。我们也可以看到在格式化输出字符串之后,printf(3) 调用了 write(2)

strace 可以开始一个新进程,也可以附加到一个已经运行的进程上。你可以通过不同程序的系统调用学到很多的东西。例如,sshd 守护进程一天都在干什么?

~/code/x86-os$ ps ax | grep sshd
12218 ?        Ss     0:00 /usr/sbin/sshd -D

~/code/x86-os$ sudo strace -p 12218
Process 12218 attached - interrupt to quit
select(7, [3 4], NULL, NULL, NULL

[
  ... nothing happens ...
  No fun, it's just waiting for a connection using select(2)
  If we wait long enough, we might see new keys being generated and so on, but
  let's attach again, tell strace to follow forks (-f), and connect via SSH
]

~/code/x86-os$ sudo strace -p 12218 -f

[lots of calls happen during an SSH login, only a few shown]

[pid 14692] read(3, "-----BEGIN RSA PRIVATE KEY-----\n"..., 1024) = 1024
[pid 14692] open("/usr/share/ssh/blacklist.RSA-2048", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory)
[pid 14692] open("/etc/ssh/blacklist.RSA-2048", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory)
[pid 14692] open("/etc/ssh/ssh_host_dsa_key", O_RDONLY|O_LARGEFILE) = 3
[pid 14692] open("/etc/protocols", O_RDONLY|O_CLOEXEC) = 4
[pid 14692] read(4, "# Internet (IP) protocols\n#\n# Up"..., 4096) = 2933
[pid 14692] open("/etc/hosts.allow", O_RDONLY) = 4
[pid 14692] open("/lib/i386-linux-gnu/libnss_dns.so.2", O_RDONLY|O_CLOEXEC) = 4
[pid 14692] stat64("/etc/pam.d", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0
[pid 14692] open("/etc/pam.d/common-password", O_RDONLY|O_LARGEFILE) = 8
[pid 14692] open("/etc/pam.d/other", O_RDONLY|O_LARGEFILE) = 4

看懂 SSH 的调用是块难啃的骨头,但是,如果搞懂它你就学会了跟踪。能够看到应用程序打开的是哪个文件是有用的(“这个配置是从哪里来的?”)。如果你有一个出现错误的进程,你可以 strace 它,然后去看它通过系统调用做了什么?当一些应用程序意外退出而没有提供适当的错误信息时,你可以去检查它是否有系统调用失败。你也可以使用过滤器,查看每个调用的次数,等等:

~/code/x86-os$ strace -T -e trace=recv curl -silent www.google.com. > /dev/null

recv(3, "HTTP/1.1 200 OK
Date: Wed, 05 N"..., 16384, 0) = 4164 <0.000007>
recv(3, "fl a{color:#36c}a:visited{color:"..., 16384, 0) = 2776 <0.000005>
recv(3, "adient(top,#4d90fe,#4787ed);filt"..., 16384, 0) = 4164 <0.000007>
recv(3, "gbar.up.spd(b,d,1,!0);break;case"..., 16384, 0) = 2776 <0.000006>
recv(3, "$),a.i.G(!0)),window.gbar.up.sl("..., 16384, 0) = 1388 <0.000004>
recv(3, "margin:0;padding:5px 8px 0 6px;v"..., 16384, 0) = 1388 <0.000007>
recv(3, "){window.setTimeout(function(){v"..., 16384, 0) = 1484 <0.000006>

我鼓励你在你的操作系统中的试验这些工具。把它们用好会让你觉得自己有超能力。

但是,足够有用的东西,往往要让我们深入到它的设计中。我们可以看到那些用户空间中的应用程序是被严格限制在它自己的虚拟地址空间里,运行在 Ring 3(非特权模式)中。一般来说,只涉及到计算和内存访问的任务是不需要请求系统调用的。例如,像 strlen(3)memcpy(3) 这样的 C 库函数并不需要内核去做什么。这些都是在应用程序内部发生的事。

C 库函数的 man 页面所在的节(即圆括号里的 23)也提供了线索。节 2 是用于系统调用封装,而节 3 包含了其它 C 库函数。但是,正如我们在 printf(3) 中所看到的,库函数最终可以产生一个或者多个系统调用。

如果你对此感到好奇,这里是 Linux (也有 Filippo 的列表)和 Windows 的全部系统调用列表。它们各自有大约 310 和 460 个系统调用。看这些系统调用是非常有趣的,因为,它们代表了软件在现代的计算机上能够做什么。另外,你还可能在这里找到与进程间通讯和性能相关的“宝藏”。这是一个“不懂 Unix 的人注定最终还要重新发明一个蹩脚的 Unix ” 的地方。(LCTT 译注:原文 “Those who do not understand Unix are condemned to reinvent it,poorly。” 这句话是 Henry Spencer 的名言,反映了 Unix 的设计哲学,它的一些理念和文化是一种技术发展的必须结果,看似糟糕却无法超越。)

与 CPU 周期相比,许多系统调用花很长的时间去执行任务,例如,从一个硬盘驱动器中读取内容。在这种情况下,调用进程在底层的工作完成之前一直处于休眠状态。因为,CPU 运行的非常快,一般的程序都因为 I/O 的限制在它的生命周期的大部分时间处于休眠状态,等待系统调用返回。相反,如果你跟踪一个计算密集型任务,你经常会看到没有任何的系统调用参与其中。在这种情况下,top(1) 将显示大量的 CPU 使用。

在一个系统调用中的开销可能会是一个问题。例如,固态硬盘比普通硬盘要快很多,但是,操作系统的开销可能比 I/O 操作本身的开销 更加昂贵。执行大量读写操作的程序可能就是操作系统开销的瓶颈所在。向量化 I/O 对此有一些帮助。因此要做 文件的内存映射,它允许一个程序仅访问内存就可以读或写磁盘文件。类似的映射也存在于像视频卡这样的地方。最终,云计算的经济性可能导致内核消除或最小化用户模式/内核模式的切换。

最终,系统调用还有益于系统安全。一是,无论如何来历不明的一个二进制程序,你都可以通过观察它的系统调用来检查它的行为。这种方式可能用于去检测恶意程序。例如,我们可以记录一个未知程序的系统调用的策略,并对它的异常行为进行报警,或者对程序调用指定一个白名单,这样就可以让漏洞利用变得更加困难。在这个领域,我们有大量的研究,和许多工具,但是没有“杀手级”的解决方案。

这就是系统调用。很抱歉这篇文章有点长,我希望它对你有用。接下来的时间,我将写更多(短的)文章,也可以在 RSSTwitter 关注我。这篇文章献给 glorious Clube Atlético Mineiro。


via:https://manybutfinite.com/post/system-calls/

作者:Gustavo Duarte 译者:qhwdw 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

介绍

这篇文章是我在 CI 环境(特别是在 Gitlab 中)的 Docker 容器中构建 Go 项目的研究总结。我发现很难解决私有依赖问题(来自 Node/.NET 背景),因此这是我写这篇文章的主要原因。如果 Docker 镜像上存在任何问题或提交请求,请随时与我们联系。

dep

由于 dep 是现在管理 Go 依赖关系的最佳选择,因此在构建前之前运行 dep ensure

注意:我个人不会将我的 vendor/ 文件夹提交到源码控制,如果你这样做,我不确定这个步骤是否可以跳过。

使用 Docker 构建的最好方法是使用 dep ensure -vendor-only见这里

Docker 构建镜像

我第一次尝试使用 golang:1.10,但这个镜像没有:

  • curl
  • git
  • make
  • dep
  • golint

我已经创建好了用于构建的镜像(github / dockerhub),我会保持更新,但我不提供任何担保,因此你应该创建并管理自己的 Dockerhub。

内部依赖关系

我们完全有能力创建一个有公共依赖关系的项目。但是如果你的项目依赖于另一个私人 Gitlab 仓库呢?

在本地运行 dep ensure 应该可以和你的 git 设置一起工作,但是一旦在 CI 上不适用,构建就会失败。

Gitlab 权限模型

这是在 Gitlab 8.12 中添加的,这个我们最关心的有用的功能是在构建期提供的 CI_JOB_TOKEN 环境变量。

这基本上意味着我们可以像这样克隆依赖仓库

git clone https://gitlab-ci-token:${CI_JOB_TOKEN}@gitlab.com/myuser/mydependentrepo

然而,我们希望使这更友好一点,因为 dep 在试图拉取代码时不会奇迹般地添加凭据。

我们将把这一行添加到 .gitlab-ci.ymlbefore_script 部分。

before_script:
  - echo -e "machine gitlab.com\nlogin gitlab-ci-token\npassword ${CI_JOB_TOKEN}" > ~/.netrc

使用 .netrc 文件可以指定哪个凭证用于哪个服务器。这种方法可以避免每次从 Git 中拉取(或推送)时输入用户名和密码。密码以明文形式存储,因此你不应在自己的计算机上执行此操作。这实际用于 Git 在背后使用 cURL在这里阅读更多

项目文件

Makefile

虽然这是可选的,但我发现它使事情变得更容易。

配置这些步骤意味着在 CI 脚本(和本地)中,我们可以运行 make lintmake build 等,而无需每次重复步骤。

GOFILES = $(shell find . -name '*.go' -not -path './vendor/*')
GOPACKAGES = $(shell go list ./...  | grep -v /vendor/)

default: build

workdir:
    mkdir -p workdir

build: workdir/scraper

workdir/scraper: $(GOFILES)
    GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -o workdir/scraper .

test: test-all

test-all:
    @go test -v $(GOPACKAGES)

lint: lint-all

lint-all:
    @golint -set_exit_status $(GOPACKAGES)

.gitlab-ci.yml

这是 Gitlab CI 魔术发生的地方。你可能想使用自己的镜像。

image: sjdweb/go-docker-build:1.10

stages:
  - test
  - build

before_script:
  - cd $GOPATH/src
  - mkdir -p gitlab.com/$CI_PROJECT_NAMESPACE
  - cd gitlab.com/$CI_PROJECT_NAMESPACE
  - ln -s $CI_PROJECT_DIR
  - cd $CI_PROJECT_NAME
  - echo -e "machine gitlab.com\nlogin gitlab-ci-token\npassword ${CI_JOB_TOKEN}" > ~/.netrc
  - dep ensure -vendor-only

lint_code:
  stage: test
  script:
    - make lint

unit_tests:
  stage: test
  script:
    - make test

build:
  stage: build
  script:
    - make

缺少了什么

我通常会用我的二进制文件构建 Docker 镜像,并将其推送到 Gitlab 容器注册库中。

你可以看到我正在构建二进制文件并退出,你至少需要将该二进制文件(例如生成文件)存储在某处。


via: https://seandrumm.co.uk/blog/building-go-projects-with-docker-on-gitlab-ci/

作者:SEAN DRUMM 译者:geekpi 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

你知道吗,如何在 Linux 系统上查看帐户的创建日期?如果知道,那么有些什么办法。

你成功了么?如果是的话,该怎么做?

基本上 Linux 系统不会跟踪这些信息,因此,获取这些信息的替代方法是什么?

你可能会问为什么我要查看这个?

是的,在某些情况下,你可能需要查看这些信息,那时就会对你会有帮助。

可以使用以下 7 种方法进行验证。

  • 使用 /var/log/secure
  • 使用 aureport 工具
  • 使用 .bash_logout
  • 使用 chage 命令
  • 使用 useradd 命令
  • 使用 passwd 命令
  • 使用 last 命令

方式 1:使用 /var/log/secure

它存储所有安全相关的消息,包括身份验证失败和授权特权。它还会通过系统安全守护进程跟踪 sudo 登录、SSH 登录和其他错误记录。

# grep prakash /var/log/secure
Apr 12 04:07:18 centos.2daygeek.com useradd[21263]: new group: name=prakash, GID=501
Apr 12 04:07:18 centos.2daygeek.com useradd[21263]: new user: name=prakash, UID=501, GID=501, home=/home/prakash, shell=/bin/bash
Apr 12 04:07:34 centos.2daygeek.com passwd: pam_unix(passwd:chauthtok): password changed for prakash
Apr 12 04:08:32 centos.2daygeek.com sshd[21269]: Accepted password for prakash from 103.5.134.167 port 60554 ssh2
Apr 12 04:08:32 centos.2daygeek.com sshd[21269]: pam_unix(sshd:session): session opened for user prakash by (uid=0)

方式 2:使用 aureport 工具

aureport 工具可以根据记录在审计日志中的事件记录生成汇总和柱状报告。默认情况下,它会查询 /var/log/audit/ 目录中的所有 audit.log 文件来创建报告。

# aureport --auth | grep prakash
46. 04/12/2018 04:08:32 prakash 103.5.134.167 ssh /usr/sbin/sshd yes 288
47. 04/12/2018 04:08:32 prakash 103.5.134.167 ssh /usr/sbin/sshd yes 291

方式 3:使用 .bash\_logout

家目录中的 .bash_logout 对 bash 有特殊的含义,它提供了一种在用户退出系统时执行命令的方式。

我们可以查看用户家目录中 .bash_logout 的更改日期。该文件是在用户第一次注销时创建的。

# stat /home/prakash/.bash_logout
 File: `/home/prakash/.bash_logout'
 Size: 18 Blocks: 8 IO Block: 4096 regular file
Device: 801h/2049d Inode: 256153 Links: 1
Access: (0644/-rw-r--r--) Uid: ( 501/ prakash) Gid: ( 501/ prakash)
Access: 2017-03-22 20:15:00.000000000 -0400
Modify: 2017-03-22 20:15:00.000000000 -0400
Change: 2018-04-12 04:07:18.283000323 -0400

方式 4:使用 chage 命令

chage 意即 “change age”。该命令让用户管理密码过期信息。chage 命令可以修改上次密码更改日期后需要更改密码的天数。

系统使用此信息来确定用户何时必须更改其密码。如果用户自帐户创建日期以来没有更改密码,这个就有用。

# chage --list prakash
Last password change : Apr 12, 2018
Password expires : never
Password inactive : never
Account expires : never
Minimum number of days between password change : 0
Maximum number of days between password change : 99999
Number of days of warning before password expires : 7

方式 5:使用 useradd 命令

useradd 命令用于在 Linux 中创建新帐户。默认情况下,它不会添加用户创建日期,我们必须使用 “备注” 选项添加日期。

# useradd -m prakash -c `date +%Y/%m/%d`

# grep prakash /etc/passwd
prakash:x:501:501:2018/04/12:/home/prakash:/bin/bash

方式 6:使用 passwd 命令

passwd 命令用于将密码分配给本地帐户或用户。如果用户在帐户创建后没有修改密码,那么可以使用 passwd 命令查看最后一次密码修改的日期。

# passwd -S prakash
prakash PS 2018-04-11 0 99999 7 -1 (Password set, MD5 crypt.)

方式 7:使用 last 命令

last 命令读取 /var/log/wtmp,并显示自该文件创建以来所有登录(和退出)用户的列表。

# last | grep "prakash"
prakash pts/2 103.5.134.167 Thu Apr 12 04:08 still logged in

via: https://www.2daygeek.com/how-to-check-user-created-date-on-linux/

作者:Prakash Subramanian 选题:lujun9972 译者:geekpi 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

怎样在 Linux 系统上使用 find、locate、mlocate、which、 whereis、 whatis 和 apropos 命令寻找文件。

在 Linux 系统上找到你要找的文件或命令并不难, 有很多种方法可以寻找。

find

最显然的无疑是 find 命令,并且 find 变得比过去几年更容易使用了。它过去需要一个搜索的起始位置,但是现在,如果你想将搜索限制在当下目录中,你还可以使用仅包含文件名或正则表达式的 find 命令。

$ find e*
empty
examples.desktop

这样,它就像 ls 命令一样工作,并没有做太多的搜索。

对于更专业的搜索,find 命令需要一个起点和一些搜索条件(除非你只是希望它提供该起点目录的递归列表)。命令 find -type f 从当前目录开始将递归列出所有常规文件,而 find ~nemo -type f -empty 将在 nemo 的主目录中找到空文件。

$ find ~nemo -type f -empty
/home/nemo/empty

参见:11 个好玩的 Linux 终端技巧

locate

locate 命令的名称表明它与 find 命令基本相同,但它的工作原理完全不同。find 命令可以根据各种条件 —— 名称、大小、所有者、权限、状态(如空文件)等等选择文件并作为搜索选择深度,locate 命令通过名为 /var/lib/mlocate/mlocate.db 的文件查找你要查找的内容。该数据文件会定期更新,因此你刚创建的文件的位置它可能无法找到。如果这让你感到困扰,你可以运行 updatedb 命令立即获得更新。

$ sudo updatedb

mlocate

mlocate 命令的工作类似于 locate 命令,它使用与 locate 相同的 mlocate.db 文件。

which

which 命令的工作方式与 find 命令和 locate 命令有很大的区别。它使用你的搜索路径($PATH)并检查其上的每个目录中具有你要查找的文件名的可执行文件。一旦找到一个,它会停止搜索并显示该可执行文件的完整路径。

which 命令的主要优点是它回答了“如果我输入此命令,将运行什么可执行文件?”的问题。它会忽略不可执行文件,并且不会列出系统上带有该名称的所有可执行文件 —— 列出的就是它找到的第一个。如果你想查找具有某个名称的所有可执行文件,则可以像这样运行 find 命令,但是要比非常高效 which 命令用更长的时间。

$ find / -name locate -perm -a=x 2>/dev/null
/usr/bin/locate
/etc/alternatives/locate

在这个 find 命令中,我们在寻找名为 “locate” 的所有可执行文件(任何人都可以运行的文件)。我们也选择了不要查看所有“拒绝访问”的消息,否则这些消息会混乱我们的屏幕。

whereis

whereis 命令与 which 命令非常类似,但它提供了更多信息。它不仅仅是寻找可执行文件,它还寻找手册页(man page)和源文件。像 which 命令一样,它使用搜索路径($PATH) 来驱动搜索。

$ whereis locate
locate: /usr/bin/locate /usr/share/man/man1/locate.1.gz

whatis

whatis 命令有其独特的使命。它不是实际查找文件,而是在手册页中查找有关所询问命令的信息,并从手册页的顶部提供该命令的简要说明。

$ whatis locate
locate (1) - find files by name

如果你询问你刚刚设置的脚本,它不会知道你指的是什么,并会告诉你。

$ whatis cleanup
cleanup: nothing appropriate.

apropos

当你知道你想要做什么,但不知道应该使用什么命令来执行此操作时,apropos 命令很有用。例如,如果你想知道如何查找文件,那么 apropos findapropos locate 会提供很多建议。

$ apropos find
File::IconTheme (3pm) - find icon directories
File::MimeInfo::Applications (3pm) - Find programs to open a file by mimetype
File::UserDirs (3pm) - find extra media and documents directories
find (1) - search for files in a directory hierarchy
findfs (8) - find a filesystem by label or UUID
findmnt (8) - find a filesystem
gst-typefind-1.0 (1) - print Media type of file
ippfind (1) - find internet printing protocol printers
locate (1) - find files by name
mlocate (1) - find files by name
pidof (8) - find the process ID of a running program.
sane-find-scanner (1) - find SCSI and USB scanners and their device files
systemd-delta (1) - Find overridden configuration files
xdg-user-dir (1) - Find an XDG user dir
$
$ apropos locate
blkid (8) - locate/print block device attributes
deallocvt (1) - deallocate unused virtual consoles
fallocate (1) - preallocate or deallocate space to a file
IO::Tty (3pm) - Low-level allocate a pseudo-Tty, import constants.
locate (1) - find files by name
mlocate (1) - find files by name
mlocate.db (5) - a mlocate database
mshowfat (1) - shows FAT clusters allocated to file
ntfsfallocate (8) - preallocate space to a file on an NTFS volume
systemd-sysusers (8) - Allocate system users and groups
systemd-sysusers.service (8) - Allocate system users and groups
updatedb (8) - update a database for mlocate
updatedb.mlocate (8) - update a database for mlocate
whereis (1) - locate the binary, source, and manual page files for a...
which (1) - locate a command

总结

Linux 上可用于查找和识别文件的命令有很多种,但它们都非常有用。


via: https://www.networkworld.com/article/3268768/linux/finding-what-you-re-looking-for-on-linux.html

作者:Sandra Henry-Stocker 选题:lujun9972 译者:MjSeven 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出