-
Notifications
You must be signed in to change notification settings - Fork 0
/
db.go
213 lines (186 loc) · 4.85 KB
/
db.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
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
/*
* Copyright (C) 2021 Kyle Kloberdanz
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package main
import (
"database/sql"
"fmt"
"log"
"math/rand"
"sync"
_ "github.com/mattn/go-sqlite3"
"golang.org/x/sys/unix"
)
var (
mutex = &sync.Mutex{}
db *sql.DB
KFS_DB_PATH = "/home/kyle/.kfs/db/db.sqlite3"
KFS_REDUNDANCY = 2
)
func db_reduce_space(root string, size int64) {
stmt := `update disks set available = available - ? where root = ?`
_, err := db.Exec(stmt, size, root)
if err != nil {
panic(fmt.Errorf("could not update available storage record: %v", err))
}
}
func db_add_file_records(hash string, storage_dirs []string, path string) {
stmt := `
insert into files(hash, hash_algo, storage_root, path)
values(?, 'blake2b', ?, ?)
`
for _, storage_dir := range storage_dirs {
_, err := db.Exec(stmt, hash, storage_dir, path)
if err != nil {
panic(fmt.Errorf("could not add new file record: %v", err))
}
}
}
func db_has_hash(hash string) bool {
var n_records int64
query := `select count(*) from files where hash = ?`
err := db.QueryRow(query, hash).Scan(&n_records)
if err != nil {
new_err := fmt.Errorf("could not select from 'files' table: %v", err)
log.Println(new_err)
return false
}
return n_records > 0
}
func db_alloc_storage(hash string, size int64, path string) (bool, string, []string, error) {
// TODO: store file metadata in table
/*
* TODO: add a record to the sqlite db with the following metadata
* |storage root|uuid|path|filename|hash|hash algo (blake2b)|extension
* |file type|permissions|access time|modify time|change time|creation time
*/
mutex.Lock()
defer mutex.Unlock()
skip := false
// if hash already exists, then don't do anything
if db_has_hash(hash) {
skip = true
return skip, "", []string{""}, nil
}
query := `
select root
from disks
where available > ?
`
rows, err := db.Query(query, 2*size)
if err != nil {
new_err := fmt.Errorf("could not query for available disk: %v", err)
return skip, "", []string{""}, new_err
}
defer rows.Close()
var disks []string
for rows.Next() {
var root string
if err := rows.Scan(&root); err != nil {
log.Fatal(err)
}
disks = append(disks, root)
}
if len(disks) < KFS_REDUNDANCY {
new_err := fmt.Errorf(
"not enough disks to meet redundancy requirements",
)
return skip, "", []string{""}, new_err
}
rand.Shuffle(len(disks), func(i, j int) {
disks[i], disks[j] = disks[j], disks[i]
})
staging_dir := disks[0]
var storage_dirs []string
for i := 0; i < KFS_REDUNDANCY; i++ {
storage_dirs = append(storage_dirs, disks[i])
}
// reduce disk space
db_reduce_space(staging_dir, size)
for _, storage := range storage_dirs {
db_reduce_space(storage, size)
}
// add file to 'files' table
db_add_file_records(hash, storage_dirs, path)
staging_path := fmt.Sprintf("%s/.kfs/staging/", staging_dir)
var storage_paths []string
for _, dir := range storage_dirs {
full_path := fmt.Sprintf("%s/.kfs/storage/", dir)
storage_paths = append(storage_paths, full_path)
}
return skip, staging_path, storage_paths, nil
}
func db_close() {
db.Close()
}
func db_init() {
var err error
db, err = sql.Open("sqlite3", KFS_DB_PATH)
if err != nil {
panic(fmt.Errorf("failed to open database file: %v", err))
}
schemas := []string{
`
CREATE TABLE IF NOT EXISTS files(
hash TEXT,
hash_algo TEXT,
storage_root TEXT,
path TEXT,
filename TEXT,
extension TEXT
);
`,
`
CREATE TABLE IF NOT EXISTS disks(
root TEXT NOT NULL PRIMARY KEY,
available INTEGER
);
`,
}
for _, schema := range schemas {
_, err = db.Exec(schema)
if err != nil {
panic(err)
}
}
// TODO: allow user to configure disk locations
disks := []string{
"/mnt/disk1",
"/mnt/disk2",
"/mnt/disk3",
"/mnt/disk4",
}
disk_insert := `
INSERT OR REPLACE INTO disks(
root,
available
) values(?, ?)
`
for _, disk := range disks {
space := get_disk_space(disk)
_, err = db.Exec(disk_insert, disk, space)
if err != nil {
panic(err)
}
}
}
func get_disk_space(path string) uint64 {
var stat unix.Statfs_t
unix.Statfs(path, &stat)
// Available blocks * size per block = available space in bytes
available_space := stat.Bavail * uint64(stat.Bsize)
return available_space
}