1
2
3
4
19
20 package fsquota
21
22 import (
23 "bufio"
24 "fmt"
25 "os"
26 "path/filepath"
27 "regexp"
28 "strconv"
29 "sync"
30
31 "golang.org/x/sys/unix"
32 "k8s.io/kubernetes/pkg/volume/util/fsquota/common"
33 )
34
35 var projectsFile = "/etc/projects"
36 var projidFile = "/etc/projid"
37
38 var projectsParseRegexp = regexp.MustCompilePOSIX("^([[:digit:]]+):(.*)$")
39 var projidParseRegexp = regexp.MustCompilePOSIX("^([^#][^:]*):([[:digit:]]+)$")
40
41 var quotaIDLock sync.RWMutex
42
43 const maxUnusedQuotasToSearch = 128
44
45 type projectType struct {
46 isValid bool
47 id common.QuotaID
48 data string
49 line string
50 }
51
52 type projectsList struct {
53 projects []projectType
54 projid []projectType
55 }
56
57 func projFilesAreOK() error {
58 if sf, err := os.Lstat(projectsFile); err != nil || sf.Mode().IsRegular() {
59 if sf, err := os.Lstat(projidFile); err != nil || sf.Mode().IsRegular() {
60 return nil
61 }
62 return fmt.Errorf("%s exists but is not a plain file, cannot continue", projidFile)
63 }
64 return fmt.Errorf("%s exists but is not a plain file, cannot continue", projectsFile)
65 }
66
67 func lockFile(file *os.File) error {
68 return unix.Flock(int(file.Fd()), unix.LOCK_EX)
69 }
70
71 func unlockFile(file *os.File) error {
72 return unix.Flock(int(file.Fd()), unix.LOCK_UN)
73 }
74
75
76
77 func openAndLockProjectFiles() (*os.File, *os.File, error) {
78
79 if err := projFilesAreOK(); err != nil {
80 return nil, nil, fmt.Errorf("system project files failed verification: %v", err)
81 }
82
83
84 fProjects, err := os.OpenFile(projectsFile, os.O_RDONLY|os.O_CREATE, 0644)
85 if err != nil {
86 err = fmt.Errorf("unable to open %s: %v", projectsFile, err)
87 return nil, nil, err
88 }
89 fProjid, err := os.OpenFile(projidFile, os.O_RDONLY|os.O_CREATE, 0644)
90 if err == nil {
91
92 if err = projFilesAreOK(); err == nil {
93 err = lockFile(fProjects)
94 if err == nil {
95 err = lockFile(fProjid)
96 if err == nil {
97 return fProjects, fProjid, nil
98 }
99
100 err = fmt.Errorf("unable to lock %s: %v", projidFile, err)
101 unlockFile(fProjects)
102 } else {
103 err = fmt.Errorf("unable to lock %s: %v", projectsFile, err)
104 }
105 } else {
106 err = fmt.Errorf("system project files failed re-verification: %v", err)
107 }
108 fProjid.Close()
109 } else {
110 err = fmt.Errorf("unable to open %s: %v", projidFile, err)
111 }
112 fProjects.Close()
113 return nil, nil, err
114 }
115
116 func closeProjectFiles(fProjects *os.File, fProjid *os.File) error {
117
118
119 var err error
120 var err1 error
121 if fProjid != nil {
122 err = fProjid.Close()
123 }
124 if fProjects != nil {
125 err1 = fProjects.Close()
126 }
127 if err == nil {
128 return err1
129 }
130 return err
131 }
132
133 func parseProject(l string) projectType {
134 if match := projectsParseRegexp.FindStringSubmatch(l); match != nil {
135 i, err := strconv.Atoi(match[1])
136 if err == nil {
137 return projectType{true, common.QuotaID(i), match[2], l}
138 }
139 }
140 return projectType{true, common.BadQuotaID, "", l}
141 }
142
143 func parseProjid(l string) projectType {
144 if match := projidParseRegexp.FindStringSubmatch(l); match != nil {
145 i, err := strconv.Atoi(match[2])
146 if err == nil {
147 return projectType{true, common.QuotaID(i), match[1], l}
148 }
149 }
150 return projectType{true, common.BadQuotaID, "", l}
151 }
152
153 func parseProjFile(f *os.File, parser func(l string) projectType) []projectType {
154 var answer []projectType
155 scanner := bufio.NewScanner(f)
156 for scanner.Scan() {
157 answer = append(answer, parser(scanner.Text()))
158 }
159 return answer
160 }
161
162 func readProjectFiles(projects *os.File, projid *os.File) projectsList {
163 return projectsList{parseProjFile(projects, parseProject), parseProjFile(projid, parseProjid)}
164 }
165
166
167
168
169 func findAvailableQuota(path string, idMap map[common.QuotaID]bool) (common.QuotaID, error) {
170 unusedQuotasSearched := 0
171 for id := common.FirstQuota; true; id++ {
172 if _, ok := idMap[id]; !ok {
173 isInUse, err := getApplier(path).QuotaIDIsInUse(id)
174 if err != nil {
175 return common.BadQuotaID, err
176 } else if !isInUse {
177 return id, nil
178 }
179 unusedQuotasSearched++
180 if unusedQuotasSearched > maxUnusedQuotasToSearch {
181 break
182 }
183 }
184 }
185 return common.BadQuotaID, fmt.Errorf("cannot find available quota ID")
186 }
187
188 func addDirToProject(path string, id common.QuotaID, list *projectsList) (common.QuotaID, bool, error) {
189 idMap := make(map[common.QuotaID]bool)
190 for _, project := range list.projects {
191 if project.data == path {
192 if id != common.BadQuotaID && id != project.id {
193 return common.BadQuotaID, false, fmt.Errorf("attempt to reassign project ID for %s", path)
194 }
195
196
197
198 return project.id, false, nil
199 }
200 idMap[project.id] = true
201 }
202 var needToAddProjid = true
203 for _, projid := range list.projid {
204 idMap[projid.id] = true
205 if projid.id == id && id != common.BadQuotaID {
206 needToAddProjid = false
207 }
208 }
209 var err error
210 if id == common.BadQuotaID {
211 id, err = findAvailableQuota(path, idMap)
212 if err != nil {
213 return common.BadQuotaID, false, err
214 }
215 needToAddProjid = true
216 }
217 if needToAddProjid {
218 name := fmt.Sprintf("volume%v", id)
219 line := fmt.Sprintf("%s:%v", name, id)
220 list.projid = append(list.projid, projectType{true, id, name, line})
221 }
222 line := fmt.Sprintf("%v:%s", id, path)
223 list.projects = append(list.projects, projectType{true, id, path, line})
224 return id, needToAddProjid, nil
225 }
226
227 func removeDirFromProject(path string, id common.QuotaID, list *projectsList) (bool, error) {
228 if id == common.BadQuotaID {
229 return false, fmt.Errorf("attempt to remove invalid quota ID from %s", path)
230 }
231 foundAt := -1
232 countByID := make(map[common.QuotaID]int)
233 for i, project := range list.projects {
234 if project.data == path {
235 if id != project.id {
236 return false, fmt.Errorf("attempting to remove quota ID %v from path %s, but expecting ID %v", id, path, project.id)
237 } else if foundAt != -1 {
238 return false, fmt.Errorf("found multiple quota IDs for path %s", path)
239 }
240
241 list.projects[i].isValid = false
242 foundAt = i
243 }
244 countByID[project.id]++
245 }
246 if foundAt == -1 {
247 return false, fmt.Errorf("cannot find quota associated with path %s", path)
248 }
249 if countByID[id] <= 1 {
250
251
252 for i, projid := range list.projid {
253 if projid.id == id {
254 list.projid[i].isValid = false
255 }
256 }
257 return true, nil
258 }
259 return false, nil
260 }
261
262 func writeProjectFile(base *os.File, projects []projectType) (string, error) {
263 oname := base.Name()
264 stat, err := base.Stat()
265 if err != nil {
266 return "", err
267 }
268 mode := stat.Mode() & os.ModePerm
269 f, err := os.CreateTemp(filepath.Dir(oname), filepath.Base(oname))
270 if err != nil {
271 return "", err
272 }
273 filename := f.Name()
274 if err := os.Chmod(filename, mode); err != nil {
275 return "", err
276 }
277 for _, proj := range projects {
278 if proj.isValid {
279 if _, err := f.WriteString(fmt.Sprintf("%s\n", proj.line)); err != nil {
280 f.Close()
281 os.Remove(filename)
282 return "", err
283 }
284 }
285 }
286 if err := f.Close(); err != nil {
287 os.Remove(filename)
288 return "", err
289 }
290 return filename, nil
291 }
292
293 func writeProjectFiles(fProjects *os.File, fProjid *os.File, writeProjid bool, list projectsList) error {
294 tmpProjects, err := writeProjectFile(fProjects, list.projects)
295 if err == nil {
296
297 if writeProjid {
298 tmpProjid, err := writeProjectFile(fProjid, list.projid)
299 if err == nil {
300 err = os.Rename(tmpProjid, fProjid.Name())
301 if err != nil {
302 os.Remove(tmpProjid)
303 }
304 }
305 }
306 if err == nil {
307 err = os.Rename(tmpProjects, fProjects.Name())
308 if err == nil {
309 return nil
310 }
311
312
313
314
315
316
317 }
318 os.Remove(tmpProjects)
319 }
320 return fmt.Errorf("unable to write project files: %v", err)
321 }
322
323
324 func createProjectID(path string, ID common.QuotaID) (common.QuotaID, error) {
325 quotaIDLock.Lock()
326 defer quotaIDLock.Unlock()
327 fProjects, fProjid, err := openAndLockProjectFiles()
328 if err == nil {
329 defer closeProjectFiles(fProjects, fProjid)
330 list := readProjectFiles(fProjects, fProjid)
331 var writeProjid bool
332 ID, writeProjid, err = addDirToProject(path, ID, &list)
333 if err == nil && ID != common.BadQuotaID {
334 if err = writeProjectFiles(fProjects, fProjid, writeProjid, list); err == nil {
335 return ID, nil
336 }
337 }
338 }
339 return common.BadQuotaID, fmt.Errorf("createProjectID %s %v failed %v", path, ID, err)
340 }
341
342 func removeProjectID(path string, ID common.QuotaID) error {
343 if ID == common.BadQuotaID {
344 return fmt.Errorf("attempting to remove invalid quota ID %v", ID)
345 }
346 quotaIDLock.Lock()
347 defer quotaIDLock.Unlock()
348 fProjects, fProjid, err := openAndLockProjectFiles()
349 if err == nil {
350 defer closeProjectFiles(fProjects, fProjid)
351 list := readProjectFiles(fProjects, fProjid)
352 var writeProjid bool
353 writeProjid, err = removeDirFromProject(path, ID, &list)
354 if err == nil {
355 if err = writeProjectFiles(fProjects, fProjid, writeProjid, list); err == nil {
356 return nil
357 }
358 }
359 }
360 return fmt.Errorf("removeProjectID %s %v failed %v", path, ID, err)
361 }
362
View as plain text