Files
cloudpods/pkg/hostimage/image.go
2019-09-28 16:55:52 +08:00

206 lines
4.3 KiB
Go

// Copyright 2019 Yunion
//
// 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 hostimage
/*
#cgo pkg-config: glib-2.0 zlib
#include "libqemuio.h"
#include "qemu/osdep.h"
*/
import "C"
import (
"fmt"
"io"
"os"
"sync"
"sync/atomic"
"unsafe"
)
var qemuBlkCache sync.Map
type QemuioBlkDev struct {
imagePath string
readonly bool
refCount int32
blk *C.struct_QemuioBlk
}
func init() {
C.qemuio_init()
}
func (qb *QemuioBlkDev) ReadQcow2(offset int64, count int64) ([]byte, int64) {
if qb.blk == nil || offset < 0 || count < 0 {
return nil, -1
}
var b = make([]byte, count)
var total = C.int64_t(0)
ret := C.read_qcow2(qb.blk, unsafe.Pointer(&b[0]), C.int64_t(offset), C.int64_t(count), &total)
if ret < 0 {
return nil, int64(ret)
} else {
return b, int64(total)
}
}
func OpenQcow2(imagePath string, readonly bool) *QemuioBlkDev {
key := fmt.Sprintf("%s_%v", imagePath, readonly)
if blk, ok := qemuBlkCache.Load(key); ok {
qb := blk.(*QemuioBlkDev)
atomic.AddInt32(&qb.refCount, 1)
return qb
}
qb := &QemuioBlkDev{
imagePath: imagePath,
readonly: readonly,
}
cImagePath := C.CString(imagePath)
blk := C.open_qcow2(cImagePath, C.bool(readonly))
C.free(unsafe.Pointer(cImagePath))
qb.blk = blk
qb.refCount = 1
qemuBlkCache.Store(key, qb)
return qb
}
func LoadQcow2(imagePath string, readonly bool) *QemuioBlkDev {
key := fmt.Sprintf("%s_%v", imagePath, readonly)
if blk, ok := qemuBlkCache.Load(key); ok {
return blk.(*QemuioBlkDev)
}
return nil
}
func (qb *QemuioBlkDev) Qcow2GetLength() int64 {
if qb.blk == nil {
return -1
} else {
return int64(C.qcow2_get_length(qb.blk))
}
}
func (qb *QemuioBlkDev) CloseQcow2() {
if qb.blk != nil && atomic.AddInt32(&qb.refCount, -1) == 0 {
C.close_qcow2(qb.blk)
}
}
type IImage interface {
// Open image file and its backing file (if have)
Open(imagePath string, readonly bool) error
// load opend qcow2 img form qemu blk cache
Load(imagePath string, readonly bool) error
// Close may not really close image file handle, just reudce ref count
Close()
// If return number < 0 indicate read failed
Read(offset, count int64) ([]byte, int64)
// Get image file length, not file actual length, it's image virtual size
Length() int64
}
type SQcow2Image struct {
fd *QemuioBlkDev
}
func (img *SQcow2Image) Open(imagePath string, readonly bool) error {
fd := OpenQcow2(imagePath, readonly)
if fd == nil {
return fmt.Errorf("open image %s failed", imagePath)
} else {
img.fd = fd
return nil
}
}
func (img *SQcow2Image) Load(imagePath string, readonly bool) error {
fd := LoadQcow2(imagePath, readonly)
if fd == nil {
return fmt.Errorf("image %s readonly: %v not found", imagePath, readonly)
} else {
img.fd = fd
return nil
}
}
func (img *SQcow2Image) Read(offset, count int64) ([]byte, int64) {
return img.fd.ReadQcow2(offset, count)
}
func (img *SQcow2Image) Close() {
img.fd.CloseQcow2()
}
func (img *SQcow2Image) Length() int64 {
return img.fd.Qcow2GetLength()
}
type SFile struct {
fd *os.File
}
func (f *SFile) Open(imagePath string, readonly bool) error {
var mode = os.O_RDWR
if readonly {
mode = os.O_RDONLY
}
fd, err := os.OpenFile(imagePath, mode, 0644)
if err != nil {
return err
} else {
f.fd = fd
return nil
}
}
func (f *SFile) Load(imagePath string, readonly bool) error {
return fmt.Errorf("File don't support load")
}
func (f *SFile) Read(offset, count int64) ([]byte, int64) {
buf := make([]byte, count)
var readCount int64 = 0
for readCount < count {
cnt, err := f.fd.Read(buf[readCount:])
readCount += int64(cnt)
if err == io.EOF {
return buf[0:readCount], readCount
}
if err != nil {
return nil, -1
}
}
return buf, readCount
}
func (f *SFile) Close() {
f.fd.Close()
}
func (f *SFile) Length() int64 {
stat, e := f.fd.Stat()
if e != nil {
return -1
}
return stat.Size()
}