xref: /plan9-contrib/sys/src/lib9p/file.c (revision ec59a3ddbfceee0efe34584c2c9981a5e5ff1ec4)
1 #include <u.h>
2 #include <libc.h>
3 #include <auth.h>
4 #include <fcall.h>
5 #include <thread.h>
6 #include <9p.h>
7 
8 /*
9  * To avoid deadlock, the following rules must be followed.
10  * Always lock child then parent, never parent then child.
11  * If holding the free file lock, do not lock any Files.
12  */
13 struct Filelist
14 {
15 	File *f;
16 	Filelist *link;
17 };
18 
19 struct Readdir
20 {
21 	File *dir;
22 	Filelist *fl;
23 };
24 
25 static QLock filelk;
26 static File *freefilelist;
27 
28 static File*
29 allocfile(void)
30 {
31 	int i, a;
32 	File *f;
33 	enum { N = 16 };
34 
35 	qlock(&filelk);
36 	if(freefilelist == nil){
37 		f = emalloc9p(N*sizeof(*f));
38 		for(i=0; i<N-1; i++)
39 			f[i].aux = &f[i+1];
40 		f[N-1].aux = nil;
41 		f[0].allocd = 1;
42 		freefilelist = f;
43 	}
44 
45 	f = freefilelist;
46 	freefilelist = f->aux;
47 	qunlock(&filelk);
48 
49 	a = f->allocd;
50 	memset(f, 0, sizeof *f);
51 	f->allocd = a;
52 	return f;
53 }
54 
55 static void
56 freefile(File *f)
57 {
58 	Filelist *fl, *flnext;
59 
60 	for(fl=f->filelist; fl; fl=flnext){
61 		flnext = fl->link;
62 		assert(fl->f == nil);
63 		free(fl);
64 	}
65 
66 	free(f->name);
67 	free(f->uid);
68 	free(f->gid);
69 	free(f->muid);
70 	qlock(&filelk);
71 	assert(f->ref == 0);
72 	f->aux = freefilelist;
73 	freefilelist = f;
74 	qunlock(&filelk);
75 }
76 
77 static void
78 cleanfilelist(File *f)
79 {
80 	Filelist **l;
81 	Filelist *fl;
82 
83 	/*
84 	 * can't delete filelist structures while there
85 	 * are open readers of this directory, because
86 	 * they might have references to the structures.
87 	 * instead, just leave the empty refs in the list
88 	 * until there is no activity and then clean up.
89 	 */
90 	if(f->readers.ref != 0)
91 		return;
92 	if(f->nxchild == 0)
93 		return;
94 
95 	/*
96 	 * no dir readers, file is locked, and
97 	 * there are empty entries in the file list.
98 	 * clean them out.
99 	 */
100 	for(l=&f->filelist; fl=*l; ){
101 		if(fl->f == nil){
102 			*l = (*l)->link;
103 			free(fl);
104 		}else
105 			l = &(*l)->link;
106 	}
107 	f->nxchild = 0;
108 }
109 
110 void
111 closefile(File *f)
112 {
113 	if(decref(f) == 0){
114 		f->tree->destroy(f);
115 		freefile(f);
116 	}
117 }
118 
119 static void
120 nop(File*)
121 {
122 }
123 
124 int
125 removefile(File *f)
126 {
127 	File *fp;
128 	Filelist *fl;
129 
130 	fp = f->parent;
131 	if(fp == nil){
132 		werrstr("no parent");
133 		closefile(f);
134 		return -1;
135 	}
136 
137 	if(fp == f){
138 		werrstr("cannot remove root");
139 		closefile(f);
140 		return -1;
141 	}
142 
143 	wlock(f);
144 	wlock(fp);
145 	if(f->nchild != 0){
146 		werrstr("has children");
147 		wunlock(fp);
148 		wunlock(f);
149 		closefile(f);
150 		return -1;
151 	}
152 
153 	if(f->parent != fp){
154 		werrstr("parent changed underfoot");
155 		wunlock(fp);
156 		wunlock(f);
157 		closefile(f);
158 		return -1;
159 	}
160 
161 	for(fl=fp->filelist; fl; fl=fl->link)
162 		if(fl->f == f)
163 			break;
164 	assert(fl != nil && fl->f == f);
165 
166 	fl->f = nil;
167 	fp->nchild--;
168 	fp->nxchild++;
169 	f->parent = nil;
170 	wunlock(f);
171 
172 	cleanfilelist(fp);
173 	wunlock(fp);
174 
175 	closefile(fp);	/* reference from child */
176 	closefile(f);	/* reference from tree */
177 	closefile(f);
178 	return 0;
179 }
180 
181 File*
182 createfile(File *fp, char *name, char *uid, ulong perm, void *aux)
183 {
184 	File *f;
185 	Filelist **l, *fl;
186 	Tree *t;
187 
188 	if((fp->qid.type&QTDIR) == 0){
189 		werrstr("create in non-directory");
190 		return nil;
191 	}
192 
193 	wlock(fp);
194 	/*
195 	 * We might encounter blank spots along the
196 	 * way due to deleted files that have not yet
197 	 * been flushed from the file list.  Don't reuse
198 	 * those - some apps (e.g., omero) depend on
199 	 * the file order reflecting creation order.
200 	 * Always create at the end of the list.
201 	 */
202 	for(l=&fp->filelist; fl=*l; l=&fl->link){
203 		if(fl->f && strcmp(fl->f->name, name) == 0){
204 			wunlock(fp);
205 			werrstr("file already exists");
206 			return nil;
207 		}
208 	}
209 
210 	fl = emalloc9p(sizeof *fl);
211 	*l = fl;
212 
213 	f = allocfile();
214 	f->name = estrdup9p(name);
215 	f->uid = estrdup9p(uid ? uid : fp->uid);
216 	f->gid = estrdup9p(fp->gid);
217 	f->muid = estrdup9p(uid ? uid : "unknown");
218 	f->aux = aux;
219 	f->mode = perm;
220 
221 	t = fp->tree;
222 	lock(&t->genlock);
223 	f->qid.path = t->qidgen++;
224 	unlock(&t->genlock);
225 	if(perm & DMDIR)
226 		f->qid.type |= QTDIR;
227 	if(perm & DMAPPEND)
228 		f->qid.type |= QTAPPEND;
229 	if(perm & DMEXCL)
230 		f->qid.type |= QTEXCL;
231 
232 	f->mode = perm;
233 	f->atime = f->mtime = time(0);
234 	f->length = 0;
235 	f->parent = fp;
236 	incref(fp);
237 	f->tree = fp->tree;
238 
239 	incref(f);	/* being returned */
240 	incref(f);	/* for the tree */
241 	fl->f = f;
242 	fp->nchild++;
243 	wunlock(fp);
244 
245 	return f;
246 }
247 
248 static File*
249 walkfile1(File *dir, char *elem)
250 {
251 	File *fp;
252 	Filelist *fl;
253 
254 	rlock(dir);
255 	if(strcmp(elem, "..") == 0){
256 		fp = dir->parent;
257 		incref(fp);
258 		runlock(dir);
259 		closefile(dir);
260 		return fp;
261 	}
262 
263 	fp = nil;
264 	for(fl=dir->filelist; fl; fl=fl->link)
265 		if(fl->f && strcmp(fl->f->name, elem)==0){
266 			fp = fl->f;
267 			incref(fp);
268 			break;
269 		}
270 
271 	runlock(dir);
272 	closefile(dir);
273 	return fp;
274 }
275 
276 File*
277 walkfile(File *f, char *path)
278 {
279 	char *os, *s, *nexts;
280 	File *nf;
281 
282 	if(strchr(path, '/') == nil)
283 		return walkfile1(f, path);	/* avoid malloc */
284 
285 	os = s = estrdup9p(path);
286 	incref(f);
287 	for(; *s; s=nexts){
288 		if(nexts = strchr(s, '/'))
289 			*nexts++ = '\0';
290 		else
291 			nexts = s+strlen(s);
292 		nf = walkfile1(f, s);
293 		closefile(f);
294 		f = nf;
295 		if(f == nil)
296 			break;
297 	}
298 	free(os);
299 	return f;
300 }
301 
302 Tree*
303 alloctree(char *uid, char *gid, ulong mode, void (*destroy)(File*))
304 {
305 	char *muid;
306 	Tree *t;
307 	File *f;
308 
309 	t = emalloc9p(sizeof *t);
310 	f = allocfile();
311 	f->name = estrdup9p("/");
312 	if(uid == nil){
313 		uid = getuser();
314 		if(uid == nil)
315 			uid = "none";
316 	}
317 	uid = estrdup9p(uid);
318 
319 	if(gid == nil)
320 		gid = estrdup9p(uid);
321 	else
322 		gid = estrdup9p(gid);
323 
324 	muid = estrdup9p(uid);
325 
326 	f->qid = (Qid){0, 0, QTDIR};
327 	f->length = 0;
328 	f->atime = f->mtime = time(0);
329 	f->mode = DMDIR | mode;
330 	f->tree = t;
331 	f->parent = f;
332 	f->uid = uid;
333 	f->gid = gid;
334 	f->muid = muid;
335 
336 	incref(f);
337 	t->root = f;
338 	t->qidgen = 0;
339 	t->dirqidgen = 1;
340 	if(destroy == nil)
341 		destroy = nop;
342 	t->destroy = destroy;
343 
344 	return t;
345 }
346 
347 static void
348 _freefiles(File *f)
349 {
350 	Filelist *fl, *flnext;
351 
352 	for(fl=f->filelist; fl; fl=flnext){
353 		flnext = fl->link;
354 		_freefiles(fl->f);
355 		free(fl);
356 	}
357 
358 	f->tree->destroy(f);
359 	freefile(f);
360 }
361 
362 void
363 freetree(Tree *t)
364 {
365 	_freefiles(t->root);
366 	free(t);
367 }
368 
369 Readdir*
370 opendirfile(File *dir)
371 {
372 	Readdir *r;
373 
374 	rlock(dir);
375 	if((dir->mode & DMDIR)==0){
376 		runlock(dir);
377 		return nil;
378 	}
379 	r = emalloc9p(sizeof(*r));
380 
381 	/*
382 	 * This reference won't go away while we're
383 	 * using it because file list entries are not freed
384 	 * until the directory is removed and all refs to
385 	 * it (our fid is one!) have gone away.
386 	 */
387 	r->fl = dir->filelist;
388 	r->dir = dir;
389 	incref(&dir->readers);
390 	runlock(dir);
391 	return r;
392 }
393 
394 long
395 readdirfile(Readdir *r, uchar *buf, long n)
396 {
397 	long x, m;
398 	Filelist *fl;
399 
400 	for(fl=r->fl, m=0; fl && m+2<=n; fl=fl->link, m+=x){
401 		if(fl->f == nil)
402 			x = 0;
403 		else if((x=convD2M(fl->f, buf+m, n-m)) <= BIT16SZ)
404 			break;
405 	}
406 	r->fl = fl;
407 	return m;
408 }
409 
410 void
411 closedirfile(Readdir *r)
412 {
413 	if(decref(&r->dir->readers) == 0){
414 		wlock(r->dir);
415 		cleanfilelist(r->dir);
416 		wunlock(r->dir);
417 	}
418 	free(r);
419 }
420