2010-12-30 18:17:47 +00:00
|
|
|
package schema
|
|
|
|
|
|
|
|
import (
|
2011-01-01 10:51:15 +00:00
|
|
|
"bufio"
|
2010-12-30 18:17:47 +00:00
|
|
|
"bytes"
|
2010-12-31 06:37:46 +00:00
|
|
|
"camli/blobref"
|
|
|
|
"crypto/sha1"
|
2010-12-30 18:17:47 +00:00
|
|
|
"fmt"
|
2010-12-31 06:37:46 +00:00
|
|
|
"io"
|
2010-12-30 18:17:47 +00:00
|
|
|
"json"
|
|
|
|
"os"
|
2011-01-01 10:51:15 +00:00
|
|
|
"strconv"
|
2010-12-30 18:17:47 +00:00
|
|
|
"strings"
|
2011-01-01 10:51:15 +00:00
|
|
|
"sync"
|
2010-12-31 06:37:46 +00:00
|
|
|
"time"
|
2010-12-30 18:17:47 +00:00
|
|
|
)
|
|
|
|
|
2010-12-31 06:37:46 +00:00
|
|
|
var NoCamliVersionError = os.NewError("No camliVersion key in map")
|
|
|
|
var UnimplementedError = os.NewError("Unimplemented")
|
|
|
|
|
|
|
|
type StatHasher interface {
|
|
|
|
Lstat(fileName string) (*os.FileInfo, os.Error)
|
|
|
|
Hash(fileName string) (*blobref.BlobRef, os.Error)
|
2010-12-30 18:17:47 +00:00
|
|
|
}
|
|
|
|
|
2010-12-31 06:37:46 +00:00
|
|
|
var DefaultStatHasher = &defaultStatHasher{}
|
|
|
|
|
|
|
|
type defaultStatHasher struct{}
|
|
|
|
|
|
|
|
func (d *defaultStatHasher) Lstat(fileName string) (*os.FileInfo, os.Error) {
|
|
|
|
return os.Lstat(fileName)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (d *defaultStatHasher) Hash(fileName string) (*blobref.BlobRef, os.Error) {
|
|
|
|
s1 := sha1.New()
|
|
|
|
file, err := os.Open(fileName, os.O_RDONLY, 0)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
defer file.Close()
|
|
|
|
_, err = io.Copy(s1, file)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return blobref.FromHash("sha1", s1), nil
|
|
|
|
}
|
2010-12-30 18:17:47 +00:00
|
|
|
|
|
|
|
func MapToCamliJson(m map[string]interface{}) (string, os.Error) {
|
|
|
|
version, hasVersion := m["camliVersion"]
|
|
|
|
if !hasVersion {
|
2010-12-31 06:37:46 +00:00
|
|
|
return "", NoCamliVersionError
|
2010-12-30 18:17:47 +00:00
|
|
|
}
|
|
|
|
m["camliVersion"] = 0, false
|
|
|
|
jsonBytes, err := json.MarshalIndent(m, "", " ")
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
m["camliVersion"] = version
|
|
|
|
buf := new(bytes.Buffer)
|
|
|
|
fmt.Fprintf(buf, "{\"camliVersion\": %v,\n", version)
|
|
|
|
buf.Write(jsonBytes[2:])
|
|
|
|
return string(buf.Bytes()), nil
|
|
|
|
}
|
|
|
|
|
2010-12-31 06:37:46 +00:00
|
|
|
func newMapForFileName(fileName string) map[string]interface{} {
|
|
|
|
m := make(map[string]interface{})
|
|
|
|
m["camliVersion"] = 1
|
|
|
|
m["camliType"] = "" // undefined at this point
|
2010-12-30 18:17:47 +00:00
|
|
|
|
|
|
|
lastSlash := strings.LastIndex(fileName, "/")
|
|
|
|
baseName := fileName[lastSlash+1:]
|
|
|
|
if isValidUtf8(baseName) {
|
2010-12-31 06:37:46 +00:00
|
|
|
m["fileName"] = baseName
|
2010-12-30 18:17:47 +00:00
|
|
|
} else {
|
2010-12-31 06:37:46 +00:00
|
|
|
m["fileNameBytes"] = []uint8(baseName)
|
|
|
|
}
|
|
|
|
return m
|
|
|
|
}
|
|
|
|
|
|
|
|
func populateRegularFileMap(m map[string]interface{}, fileName string, fi *os.FileInfo, sh StatHasher) os.Error {
|
|
|
|
m["camliType"] = "file"
|
|
|
|
m["size"] = fi.Size
|
|
|
|
|
|
|
|
// Build the contentParts, currently just in one big chunk.
|
|
|
|
// TODO: split mutatalbe metadata (EXIF, etc) from the payload
|
|
|
|
// bytes
|
|
|
|
blobref, err := sh.Hash(fileName)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
2010-12-30 18:17:47 +00:00
|
|
|
}
|
2010-12-31 06:37:46 +00:00
|
|
|
parts := make([]map[string]interface{}, 0)
|
|
|
|
solePart := make(map[string]interface{})
|
|
|
|
solePart["blobRef"] = blobref.String()
|
|
|
|
solePart["size"] = fi.Size
|
|
|
|
parts = append(parts, solePart)
|
|
|
|
m["contentParts"] = parts
|
|
|
|
|
|
|
|
return nil
|
2010-12-30 18:17:47 +00:00
|
|
|
}
|
|
|
|
|
2010-12-31 20:13:33 +00:00
|
|
|
func rfc3339FromNanos(epochnanos int64) string {
|
|
|
|
nanos := epochnanos % 1e9
|
|
|
|
esec := epochnanos / 1e9
|
|
|
|
t := time.SecondsToUTC(esec)
|
|
|
|
timeStr := t.Format(time.RFC3339)
|
|
|
|
if nanos == 0 {
|
|
|
|
return timeStr
|
|
|
|
}
|
|
|
|
nanoStr := fmt.Sprintf("%09d", nanos)
|
|
|
|
nanoStr = strings.TrimRight(nanoStr, "0")
|
|
|
|
return timeStr[:len(timeStr)-1] + "." + nanoStr + "Z"
|
|
|
|
}
|
|
|
|
|
2011-01-01 10:51:15 +00:00
|
|
|
func populateMap(m map[int]string, file string) {
|
|
|
|
f, err := os.Open(file, os.O_RDONLY, 0)
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
bufr := bufio.NewReader(f)
|
|
|
|
for {
|
|
|
|
line, err := bufr.ReadString('\n')
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
fmt.Printf("Read line: [%s]\n", line)
|
|
|
|
parts := strings.Split(line, ":", 4)
|
|
|
|
if len(parts) >= 3 {
|
|
|
|
idstr := parts[2]
|
|
|
|
id, err := strconv.Atoi(idstr)
|
|
|
|
if err == nil {
|
|
|
|
m[id] = parts[0]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
var uidToUsernameMap map[int]string
|
|
|
|
var getUserFromUidOnce sync.Once
|
|
|
|
func getUserFromUid(uid int) string {
|
|
|
|
getUserFromUidOnce.Do(func() {
|
|
|
|
uidToUsernameMap = make(map[int]string)
|
|
|
|
populateMap(uidToUsernameMap, "/etc/passwd")
|
|
|
|
})
|
|
|
|
return uidToUsernameMap[uid]
|
|
|
|
}
|
|
|
|
|
|
|
|
var gidToUsernameMap map[int]string
|
|
|
|
var getGroupFromGidOnce sync.Once
|
|
|
|
func getGroupFromGid(uid int) string {
|
|
|
|
getGroupFromGidOnce.Do(func() {
|
|
|
|
gidToUsernameMap = make(map[int]string)
|
|
|
|
populateMap(gidToUsernameMap, "/etc/group")
|
|
|
|
})
|
|
|
|
return gidToUsernameMap[uid]
|
|
|
|
}
|
|
|
|
|
2010-12-31 06:37:46 +00:00
|
|
|
func NewFileMap(fileName string, sh StatHasher) (map[string]interface{}, os.Error) {
|
|
|
|
if sh == nil {
|
|
|
|
sh = DefaultStatHasher
|
|
|
|
}
|
|
|
|
m := newMapForFileName(fileName)
|
|
|
|
fi, err := sh.Lstat(fileName)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Common elements (from file-common.txt)
|
|
|
|
m["unixPermission"] = fmt.Sprintf("0%o", fi.Permission())
|
|
|
|
if fi.Uid != -1 {
|
|
|
|
m["unixOwnerId"] = fi.Uid
|
2011-01-01 10:51:15 +00:00
|
|
|
if user := getUserFromUid(fi.Uid); user != "" {
|
|
|
|
m["unixOwner"] = user
|
|
|
|
}
|
2010-12-31 06:37:46 +00:00
|
|
|
}
|
|
|
|
if fi.Gid != -1 {
|
|
|
|
m["unixGroupId"] = fi.Gid
|
2011-01-01 10:51:15 +00:00
|
|
|
if group := getGroupFromGid(fi.Gid); group != "" {
|
|
|
|
m["unixGroup"] = group
|
|
|
|
}
|
2010-12-31 06:37:46 +00:00
|
|
|
}
|
|
|
|
if mtime := fi.Mtime_ns; mtime != 0 {
|
2010-12-31 20:13:33 +00:00
|
|
|
m["unixMtime"] = rfc3339FromNanos(mtime)
|
2010-12-31 06:37:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
switch {
|
|
|
|
case fi.IsRegular():
|
|
|
|
if err = populateRegularFileMap(m, fileName, fi, sh); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
case fi.IsSymlink():
|
|
|
|
case fi.IsDirectory():
|
|
|
|
case fi.IsBlock():
|
|
|
|
fallthrough
|
|
|
|
case fi.IsChar():
|
|
|
|
fallthrough
|
|
|
|
case fi.IsSocket():
|
|
|
|
fallthrough
|
|
|
|
case fi.IsFifo():
|
|
|
|
return nil, UnimplementedError
|
|
|
|
}
|
|
|
|
return m, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func isValidUtf8(s string) bool {
|
|
|
|
for _, rune := range []int(s) {
|
|
|
|
if rune == 0xfffd {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true
|
2010-12-30 18:17:47 +00:00
|
|
|
}
|