/
deb.go
141 lines (125 loc) · 3.38 KB
/
deb.go
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
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
package godeb
import (
"bytes"
"errors"
"fmt"
"github.com/blakesmith/ar"
"io"
"io/ioutil"
"path/filepath"
"strings"
)
var (
ErrInvalidDeb = errors.New("header doesn't look right, mybe not archive")
)
// Debian包是标准的Unix ar格式归档,其含两个tar包——一个保存control信息;另一个含有安装文件。
// Debian包内容按顺序如下:
// 1. `debian-binary`: 只有一行文本,记录了包格式版本号。现行版本号为`2.0`
// 2. `control`归档: 名称为`control.tar`的归档文件,包含脚本以及元信息(包名,版本号,依赖,维护人等)。使用`gzip`或`xz`格式压缩。其后缀名表明了压缩格式。
// 3. `data`归档: 名为`data.tar`的归档文件,包含实际安装的文件。使用`gzip`、`bzip2`、`lzma`或`xz`压缩格式。其后缀名表明了压缩格式。
// Deb deb包结构
type Deb struct {
Control *Control
Data *ArItem
ControlExt string `json:"control_ext"`
DataExt string `json:"data_ext"`
ArItem map[string]*ArItem
}
type Reader interface {
io.Reader
io.ReaderAt
}
// LoadDeb 加载deb包,返回*Deb结构
func LoadDeb(in Reader) (*Deb, error) {
contents, err := LoadAr(in)
if err != nil {
return nil, err
}
deb := &Deb{
ArItem: contents,
}
for name, aritem := range contents {
switch {
case strings.HasPrefix(name, "control."):
deb.ControlExt = filepath.Ext(name)
deb.Control, err = loadDebControl(aritem)
if err != nil {
return nil, err
}
case strings.HasPrefix(name, "data."):
deb.DataExt = filepath.Ext(name)
deb.Data = aritem
}
}
return deb, nil
}
// LoadAr 加载ar归档返回map[string]*ArItem
func LoadAr(in Reader) (map[string]*ArItem, error) {
contents := make(map[string]*ArItem)
header := make([]byte, 8)
if _, err := in.ReadAt(header, 0); err != nil {
return nil, err
}
if string(header) != "!<arch>\n" {
return nil, ErrInvalidDeb
}
arReader := ar.NewReader(in)
offset := int64(8)
for {
header, err := arReader.Next()
if err == io.EOF {
break
}
if err != nil {
return nil, err
}
header.Name = strings.TrimSuffix(strings.TrimSpace(header.Name), "/")
contents[header.Name] = &ArItem{
Name: header.Name,
Ext: filepath.Ext(header.Name),
Data: io.NewSectionReader(in, offset+ar.HEADER_BYTE_SIZE, header.Size),
Offset: offset,
Size: header.Size,
}
offset = offset + ar.HEADER_BYTE_SIZE + header.Size + header.Size%2
}
aritem, ok := contents["debian-binary"]
if !ok {
return nil, errors.New("package invalid: missing debina-binary")
}
versionBytes, err := ioutil.ReadAll(aritem.Data)
if err != nil {
return nil, errors.New("failed to read aritem")
}
version := string(bytes.TrimSpace(versionBytes))
if version != "2.0" {
return nil, errors.New(fmt.Sprintf("unsupported debian-binary:%s", version))
}
return contents, nil
}
func loadDebControl(aritem *ArItem) (*Control, error) {
r, closer, err := aritem.TarFile()
if err != nil {
return nil, fmt.Errorf("failed to init tarfile->%+v", err)
}
defer closer.Close()
for {
member, err := r.Next()
if err == io.EOF {
break
}
if err != nil {
return nil, fmt.Errorf("failed to read member->%+v", err)
}
name := strings.TrimLeft(member.Name, "./")
if name == "control" {
var control *Control
control, err = Parse(r)
if err != nil {
return nil, err
}
return control, nil
}
}
return nil, nil
}