Skip to content
Permalink
Browse files

Merge pull request #11 from cruise-automation/collin/cpiofs

cpiofs
  • Loading branch information
crmulliner committed Nov 26, 2019
2 parents c1dfcd7 + 04e669e commit b27199e57d1d9da2afe2b334759de6210e27f5a6
@@ -2,7 +2,7 @@ FROM golang:1.13

RUN curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(go env GOPATH)/bin v1.21.0

RUN apt update && apt -y install e2tools mtools file squashfs-tools unzip python-setuptools python-lzo
RUN apt update && apt -y install e2tools mtools file squashfs-tools unzip python-setuptools python-lzo cpio
RUN wget https://github.com/crmulliner/ubi_reader/archive/master.zip -O ubireader.zip && unzip ubireader.zip && cd ubi_reader-master && python setup.py install

WORKDIR $GOPATH/src/github.com/cruise-automation/fwanalyzer
@@ -4,11 +4,12 @@


FwAnalyzer is a tool to analyze (ext2/3/4), FAT/VFat, SquashFS, UBIFS filesystem images,
and directory content using a set of configurable rules.
cpio archives, and directory content using a set of configurable rules.
FwAnalyzer relies on [e2tools](https://github.com/crmulliner/e2tools/) for ext filesystems,
[mtools](https://www.gnu.org/software/mtools/) for FAT filesystems,
[squashfs-tools](https://github.com/plougher/squashfs-tools) for SquashFS filesystems, and
[ubi_reader](https://github.com/crmulliner/ubi_reader) for UBIFS filesystems.
[cpio](https://www.gnu.org/software/cpio/) for cpio archives.
SELinux/xattr support for ext2/3/4 images requires a patched version of [e2tools](https://github.com/crmulliner/e2tools/).

![fwanalyzer](images/fwanalyzer.png)
@@ -97,6 +98,7 @@ The `FsType` (filesystem type) field selects the backend that is used to access
- `squashfs`: to read SquashFS filesystem images (supported FsTypeOptions are: N/A)
- `ubifs`: to read UBIFS filesystem images (supported FsTypeOptions are: N/A)
- `vfatfs`: to read VFat filesystem images (supported FsTypeOptions are: N/A)
- `cpiofs`: to read cpio archives (supported FsTypeOptions are: N/A)

The FsTypeOptions allow tuning of the FsType driver.

@@ -28,6 +28,7 @@ import (

"github.com/BurntSushi/toml"

"github.com/cruise-automation/fwanalyzer/pkg/cpioparser"
"github.com/cruise-automation/fwanalyzer/pkg/dirparser"
"github.com/cruise-automation/fwanalyzer/pkg/extparser"
"github.com/cruise-automation/fwanalyzer/pkg/fsparser"
@@ -125,6 +126,8 @@ func NewFromConfig(imagepath string, cfgdata string) *Analyzer {
fsp = squashfsparser.New(imagepath)
} else if strings.EqualFold(config.GlobalConfig.FSType, "ubifs") {
fsp = ubifsparser.New(imagepath)
} else if strings.EqualFold(config.GlobalConfig.FSType, "cpiofs") {
fsp = cpioparser.New(imagepath)
} else {
panic("Cannot find an appropriate parser: " + config.GlobalConfig.FSType)
}
@@ -0,0 +1,249 @@
/*
Copyright 2019 GM Cruise LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package cpioparser

import (
"errors"
"fmt"
"os"
"os/exec"
"path"
"regexp"
"strconv"
"strings"

"github.com/cruise-automation/fwanalyzer/pkg/fsparser"
)

const (
cpioCmd = "cpio"
cpCmd = "cp"
MIN_LINE_LENGTH = 25
)

type CpioParser struct {
fileInfoReg *regexp.Regexp
devInfoReg *regexp.Regexp
fileLinkReg *regexp.Regexp
imagepath string
files map[string][]fsparser.FileInfo
}

func New(imagepath string) *CpioParser {
parser := &CpioParser{
//lrwxrwxrwx 1 0 0 19 Apr 24 2019 lib/libnss_dns.so.2 -> libnss_dns-2.18.so
fileInfoReg: regexp.MustCompile(
`^([\w-]+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+([\w\s\d:]+)\s+(.*)$`),
// crw-r--r-- 1 0 0 4, 64 Apr 24 2019 dev/ttyS0
devInfoReg: regexp.MustCompile(
`^([\w-]+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+),\s+(\d+)\s+([\w\s\d:]+)\s+(.*)$`),
fileLinkReg: regexp.MustCompile(`(\S+)\s->\s(\S+)`),
imagepath: imagepath,
}

return parser
}

func (p *CpioParser) ImageName() string {
return p.imagepath
}

var modeFlags = []struct {
pos int
chr byte
val uint64
}{
{0, '-', fsparser.S_IFREG},
{0, 's', fsparser.S_IFSOCK},
{0, 'l', fsparser.S_IFLNK},
{0, 'b', fsparser.S_IFBLK},
{0, 'd', fsparser.S_IFDIR},
{0, 'c', fsparser.S_IFCHR},
{0, 'p', fsparser.S_IFIFO},
{1, 'r', fsparser.S_IRUSR},
{2, 'w', fsparser.S_IWUSR},
{3, 'x', fsparser.S_IXUSR},
{3, 's', fsparser.S_IXUSR | fsparser.S_ISUID},
{3, 'S', fsparser.S_ISUID},
{4, 'r', fsparser.S_IRGRP},
{5, 'w', fsparser.S_IWGRP},
{6, 'x', fsparser.S_IXGRP},
{6, 's', fsparser.S_IXGRP | fsparser.S_ISGID},
{6, 'S', fsparser.S_ISGID},
{7, 'r', fsparser.S_IROTH},
{8, 'w', fsparser.S_IWOTH},
{9, 'x', fsparser.S_IXOTH},
{9, 't', fsparser.S_IXOTH | fsparser.S_ISVTX},
{9, 'T', fsparser.S_ISVTX},
}

const (
FILE_MODE_STR_LEN = 10 // such as "-rw-r--r--"
)

func parseMode(mode string) (uint64, error) {
var m uint64
if len(mode) != FILE_MODE_STR_LEN {
return 0, fmt.Errorf("parseMode: invalid mode string %s", mode)
}
for _, f := range modeFlags {
if mode[f.pos] == f.chr {
m |= f.val
}
}
return m, nil
}

// Ensure directory and file names are consistent, with no relative parts
// or trailing slash on directory names.
func normalizePath(filepath string) (dir string, name string) {
dir, name = path.Split(path.Clean(filepath))
dir = path.Clean(dir)
return
}

const (
NAME_IDX_NORMAL_FILE = 7
NAME_IDX_DEVICE_FILE = 8
)

func (p *CpioParser) parseFileLine(line string) (string, fsparser.FileInfo, error) {
reg := p.fileInfoReg
nameIdx := NAME_IDX_NORMAL_FILE
dirpath := ""
if strings.HasPrefix(line, "b") || strings.HasPrefix(line, "c") {
reg = p.devInfoReg
nameIdx = NAME_IDX_DEVICE_FILE
}
res := reg.FindAllStringSubmatch(line, -1)
var fi fsparser.FileInfo
// only normal files have a size
if nameIdx == NAME_IDX_NORMAL_FILE {
size, _ := strconv.Atoi(res[0][5])
fi.Size = int64(size)
}
fi.Mode, _ = parseMode(res[0][1])
fi.Uid, _ = strconv.Atoi(res[0][3])
fi.Gid, _ = strconv.Atoi(res[0][4])
// cpio returns relative pathnames so add leading "/"
fi.Name = "/" + res[0][nameIdx]

// fill in linktarget
if fi.IsLink() && strings.Contains(fi.Name, "->") {
rlnk := p.fileLinkReg.FindAllStringSubmatch(fi.Name, -1)
if rlnk == nil {
return "", fsparser.FileInfo{}, fmt.Errorf("can't parse LinkTarget from %s", fi.Name)
}
fi.Name = rlnk[0][1]
fi.LinkTarget = rlnk[0][2]
}

// handle root directory
if fi.Name == "/." {
dirpath = "."
fi.Name = "."
} else {
dirpath, fi.Name = normalizePath(fi.Name)
}

return dirpath, fi, nil

}

// GetDirInfo returns information on the specified directory.
func (p *CpioParser) GetDirInfo(dirpath string) ([]fsparser.FileInfo, error) {
if err := p.loadFileList(); err != nil {
return nil, err
}

return p.files[path.Clean(dirpath)], nil
}

// GetFileInfo returns information on the specified file.
func (p *CpioParser) GetFileInfo(filepath string) (fsparser.FileInfo, error) {
if err := p.loadFileList(); err != nil {
return fsparser.FileInfo{}, err
}

dirpath, name := normalizePath(filepath)
// the root is stored as "."
if dirpath == "/" && name == "" {
dirpath = "."
name = "."
}
dir := p.files[dirpath]
for _, fi := range dir {
if fi.Name == name {
return fi, nil
}
}
return fsparser.FileInfo{}, fmt.Errorf("Can't find file %s", filepath)
}

func (p *CpioParser) loadFileList() error {
if p.files != nil {
return nil
}
p.files = make(map[string][]fsparser.FileInfo)

out, err := exec.Command("sh", "-c", cpioCmd+" -tvn < "+p.imagepath).CombinedOutput()
if err != nil {
if err.Error() != errors.New("exit status 2").Error() {
fmt.Fprintf(os.Stderr, "getDirList: >%s<", err)
return err
}
}

lines := strings.Split(string(out), "\n")
for _, line := range lines {
if len(line) < MIN_LINE_LENGTH {
continue
}
if strings.HasPrefix(line, "cpio") {
continue
}
path, fi, err := p.parseFileLine(line)
if err == nil {
dirfiles := p.files[path]
dirfiles = append(dirfiles, fi)
p.files[path] = dirfiles
}
}
return nil
}

// CopyFile copies the specified file to the specified destination.
func (p *CpioParser) CopyFile(filepath string, dstdir string) bool {
out, err := exec.Command("sh", "-c", cpioCmd+" -i --to-stdout "+filepath[1:]+" < "+p.imagepath+" > "+dstdir).CombinedOutput()
if err != nil {
if err.Error() != errors.New("exit status 2").Error() {
fmt.Fprintf(os.Stderr, "cpio failed: %v: %s\n", err, out)
return false
}
}
return true
}

func (p *CpioParser) Supported() bool {
if _, err := exec.LookPath(cpioCmd); err != nil {
return false
}
if _, err := exec.LookPath(cpCmd); err != nil {
return false
}
return true
}

0 comments on commit b27199e

Please sign in to comment.
You can’t perform that action at this time.