1/*	$NetBSD: v7fs_file.c,v 1.7 2022/02/11 10:55:15 hannken Exp $	*/
2
3/*-
4 * Copyright (c) 2011 The NetBSD Foundation, Inc.
5 * All rights reserved.
6 *
7 * This code is derived from software contributed to The NetBSD Foundation
8 * by UCHIYAMA Yasushi.
9 *
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
12 * are met:
13 * 1. Redistributions of source code must retain the above copyright
14 *    notice, this list of conditions and the following disclaimer.
15 * 2. Redistributions in binary form must reproduce the above copyright
16 *    notice, this list of conditions and the following disclaimer in the
17 *    documentation and/or other materials provided with the distribution.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
20 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
21 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
23 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29 * POSSIBILITY OF SUCH DAMAGE.
30 */
31
32#if HAVE_NBTOOL_CONFIG_H
33#include "nbtool_config.h"
34#endif
35
36#include <sys/cdefs.h>
37__KERNEL_RCSID(0, "$NetBSD: v7fs_file.c,v 1.7 2022/02/11 10:55:15 hannken Exp $");
38#if defined _KERNEL_OPT
39#include "opt_v7fs.h"
40#endif
41
42#include <sys/param.h>
43#ifdef _KERNEL
44#include <sys/systm.h>
45#else
46#include <stdio.h>
47#include <string.h>
48#include <errno.h>
49#endif
50
51#include "v7fs.h"
52#include "v7fs_impl.h"
53#include "v7fs_endian.h"
54#include "v7fs_inode.h"
55#include "v7fs_dirent.h"
56#include "v7fs_file.h"
57#include "v7fs_datablock.h"
58
59#ifdef V7FS_FILE_DEBUG
60#define	DPRINTF(fmt, args...)	printf("%s: " fmt, __func__, ##args)
61#else
62#define	DPRINTF(fmt, args...)	((void)0)
63#endif
64
65static int lookup_subr(struct v7fs_self *, void *, v7fs_daddr_t, size_t);
66static int remove_subr(struct v7fs_self *, void *, v7fs_daddr_t, size_t);
67
68int
69v7fs_file_lookup_by_name(struct v7fs_self *fs, struct v7fs_inode *parent_dir,
70    const char *name, size_t namelen, v7fs_ino_t *ino)
71{
72	char filename[V7FS_NAME_MAX + 1];
73	int error;
74
75	v7fs_dirent_filename(filename, name, namelen);
76	DPRINTF("%s(%.*s) dir=%d\n", filename, (int)namelen, name,
77	    parent_dir->inode_number);
78
79	struct v7fs_lookup_arg lookup_arg = { .name = filename,
80					      .inode_number = 0 };
81	if ((error = v7fs_datablock_foreach(fs, parent_dir, lookup_subr,
82		    &lookup_arg)) != V7FS_ITERATOR_BREAK) {
83		DPRINTF("not found.\n");
84		return ENOENT;
85	}
86
87	*ino = lookup_arg.inode_number;
88	DPRINTF("done. ino=%d\n", *ino);
89
90	return 0;
91}
92
93static int
94lookup_subr(struct v7fs_self *fs, void *ctx, v7fs_daddr_t blk, size_t sz)
95{
96	struct v7fs_lookup_arg *p = (struct v7fs_lookup_arg *)ctx;
97	struct v7fs_dirent *dir;
98	const char *name = p->name;
99	void *buf;
100	size_t i, n;
101	int ret = 0;
102
103	if (!(buf = scratch_read(fs, blk)))
104		return EIO;
105
106	dir = (struct v7fs_dirent *)buf;
107	n = sz / sizeof(*dir);
108	v7fs_dirent_endian_convert(fs, dir, n);
109
110	for (i = 0; i < n; i++, dir++) {
111		if (dir->inode_number < 1) {
112			DPRINTF("*** bad inode #%d ***\n", dir->inode_number);
113			continue;
114		}
115
116		if (strncmp((const char *)dir->name, name, V7FS_NAME_MAX) == 0)
117		{
118			p->inode_number = dir->inode_number;
119			ret =  V7FS_ITERATOR_BREAK; /* found */
120			break;
121		}
122	}
123	scratch_free(fs, buf);
124
125	return ret;
126}
127
128int
129v7fs_file_allocate(struct v7fs_self *fs, struct v7fs_inode *parent_dir,
130    const char *srcname, size_t srclen, struct v7fs_fileattr *attr,
131    v7fs_ino_t *ino)
132{
133	struct v7fs_inode inode;
134	struct v7fs_dirent *dir;
135	int error;
136
137	/* Check filename. */
138	if (v7fs_file_lookup_by_name(fs, parent_dir, srcname, srclen,
139	    ino) == 0) {
140		DPRINTF("%.*s exists\n", (int)srclen, srcname);
141		return EEXIST;
142	}
143
144	/* Get new inode. */
145	if ((error = v7fs_inode_allocate(fs, ino)))
146		return error;
147
148	/* Set initial attribute. */
149	memset(&inode, 0, sizeof(inode));
150	inode.inode_number = *ino;
151	inode.mode = attr->mode;
152	inode.uid = attr->uid;
153	inode.gid = attr->gid;
154	if (attr->ctime)
155		inode.ctime = attr->ctime;
156	if (attr->mtime)
157		inode.mtime = attr->mtime;
158	if (attr->atime)
159		inode.atime = attr->atime;
160
161	switch (inode.mode & V7FS_IFMT)	{
162	default:
163		DPRINTF("Can't allocate %o type.\n", inode.mode);
164		v7fs_inode_deallocate(fs, *ino);
165		return EINVAL;
166	case V7FS_IFCHR:
167		/* FALLTHROUGH */
168	case V7FS_IFBLK:
169		inode.nlink = 1;
170		inode.device = attr->device;
171		inode.addr[0] = inode.device;
172		break;
173	case V7FSBSD_IFFIFO:
174		/* FALLTHROUGH */
175	case V7FSBSD_IFSOCK:
176		/* FALLTHROUGH */
177	case V7FSBSD_IFLNK:
178		/* FALLTHROUGH */
179	case V7FS_IFREG:
180		inode.nlink = 1;
181		break;
182	case V7FS_IFDIR:
183		inode.nlink = 2;	/* . + .. */
184		if ((error = v7fs_datablock_expand(fs, &inode, sizeof(*dir) * 2
185		    ))) {
186			v7fs_inode_deallocate(fs, *ino);
187			return error;
188		}
189		v7fs_daddr_t blk = inode.addr[0];
190		void *buf;
191		if (!(buf = scratch_read(fs, blk))) {
192			v7fs_inode_deallocate(fs, *ino);
193			return EIO;
194		}
195		dir = (struct v7fs_dirent *)buf;
196		strcpy(dir[0].name, ".");
197		dir[0].inode_number = V7FS_VAL16(fs, *ino);
198		strcpy(dir[1].name, "..");
199		dir[1].inode_number = V7FS_VAL16(fs, parent_dir->inode_number);
200		if (!fs->io.write(fs->io.cookie, buf, blk)) {
201			scratch_free(fs, buf);
202			return EIO;
203		}
204		scratch_free(fs, buf);
205		break;
206	}
207
208	v7fs_inode_writeback(fs, &inode);
209
210	/* Link this inode to parent directory. */
211	if ((error = v7fs_directory_add_entry(fs, parent_dir, *ino, srcname,
212	    srclen)))
213	{
214		DPRINTF("can't add dirent.\n");
215		return error;
216	}
217
218	return 0;
219}
220
221int
222v7fs_file_deallocate(struct v7fs_self *fs, struct v7fs_inode *parent_dir,
223    const char *name, size_t namelen)
224{
225	v7fs_ino_t ino;
226	struct v7fs_inode inode;
227	int error;
228
229	DPRINTF("%.*s\n", (int)namelen, name);
230	if ((error = v7fs_file_lookup_by_name(fs, parent_dir, name, namelen,
231	    &ino))) {
232		DPRINTF("no such a file: %s\n", name);
233		return error;
234	}
235	DPRINTF("%.*s->#%d\n", (int)namelen, name, ino);
236	if ((error = v7fs_inode_load(fs, &inode, ino)))
237		return error;
238
239	if (v7fs_inode_isdir(&inode)) {
240		char filename[V7FS_NAME_MAX + 1];
241		v7fs_dirent_filename(filename, name, namelen);
242		/* Check parent */
243		if (strncmp(filename, "..", V7FS_NAME_MAX) == 0) {
244			DPRINTF("can not remove '..'\n");
245			return EINVAL; /* t_vnops rename_dotdot */
246		}
247		/* Check empty */
248		if (v7fs_inode_filesize(&inode) !=
249		    sizeof(struct v7fs_dirent) * 2 /*"." + ".."*/) {
250			DPRINTF("directory not empty.\n");
251			return ENOTEMPTY;/* t_vnops dir_noempty, rename_dir(6)*/
252		}
253		error = v7fs_datablock_size_change(fs, 0, &inode);
254		if (error)
255			return error;
256		inode.nlink = 0;	/* remove this. */
257	} else {
258		/* Decrement reference count. */
259		--inode.nlink;	/* regular file. */
260		DPRINTF("%.*s nlink=%d\n", (int)namelen, name, inode.nlink);
261	}
262
263
264	if ((error = v7fs_directory_remove_entry(fs, parent_dir, name,
265	    namelen)))
266		return error;
267	DPRINTF("remove dirent\n");
268
269	v7fs_inode_writeback(fs, &inode);
270
271	return 0;
272}
273
274int
275v7fs_directory_add_entry(struct v7fs_self *fs, struct v7fs_inode *parent_dir,
276    v7fs_ino_t ino, const char *srcname, size_t srclen)
277{
278	struct v7fs_inode inode;
279	struct v7fs_dirent *dir;
280	int error = 0;
281	v7fs_daddr_t blk;
282	void *buf;
283	char filename[V7FS_NAME_MAX + 1];
284
285	/* Truncate filename. */
286	v7fs_dirent_filename(filename, srcname, srclen);
287	DPRINTF("%s(%.*s) %d\n", filename, (int)srclen, srcname, ino);
288
289	/* Target inode */
290	if ((error = v7fs_inode_load(fs, &inode, ino)))
291		return error;
292
293	/* Expand datablock. */
294	if ((error = v7fs_datablock_expand(fs, parent_dir, sizeof(*dir))))
295		return error;
296
297	/* Read last entry. */
298	if (!(blk = v7fs_datablock_last(fs, parent_dir,
299	    v7fs_inode_filesize(parent_dir))))
300		return EIO;
301
302	/* Load dirent block. This vnode(parent dir) is locked by VFS layer. */
303	if (!(buf = scratch_read(fs, blk)))
304		return EIO;
305
306	size_t sz = v7fs_inode_filesize(parent_dir);
307	sz = V7FS_RESIDUE_BSIZE(sz);	/* last block payload. */
308	int n = sz / sizeof(*dir) - 1;
309	/* Add dirent. */
310	dir = (struct v7fs_dirent *)buf;
311	dir[n].inode_number = V7FS_VAL16(fs, ino);
312	memcpy((char *)dir[n].name, filename, V7FS_NAME_MAX);
313	/* Write back datablock */
314	if (!fs->io.write(fs->io.cookie, buf, blk))
315		error = EIO;
316	scratch_free(fs, buf);
317
318	if (v7fs_inode_isdir(&inode)) {
319		parent_dir->nlink++;
320		v7fs_inode_writeback(fs, parent_dir);
321	}
322
323	DPRINTF("done. (dirent size=%dbyte)\n", parent_dir->filesize);
324
325	return error;
326}
327
328int
329v7fs_directory_remove_entry(struct v7fs_self *fs, struct v7fs_inode *parent_dir,
330    const char *name, size_t namelen)
331{
332	struct v7fs_inode inode;
333	char filename[V7FS_NAME_MAX + 1];
334	int error;
335	struct v7fs_dirent lastdirent;
336	v7fs_daddr_t lastblk;
337	size_t sz, lastsz;
338	v7fs_off_t pos;
339	void *buf;
340
341	v7fs_dirent_filename(filename, name, namelen);
342
343	/* Setup replaced entry. */
344	sz = parent_dir->filesize;
345	lastblk = v7fs_datablock_last(fs, parent_dir,
346	    v7fs_inode_filesize(parent_dir));
347	lastsz = V7FS_RESIDUE_BSIZE(sz);
348	pos = lastsz - sizeof(lastdirent);
349
350	if (!(buf = scratch_read(fs, lastblk)))
351		return EIO;
352	lastdirent = *((struct v7fs_dirent *)((uint8_t *)buf + pos));
353	scratch_free(fs, buf);
354	DPRINTF("last dirent=%d %s pos=%d\n",
355	    V7FS_VAL16(fs, lastdirent.inode_number), lastdirent.name, pos);
356
357	struct v7fs_lookup_arg lookup_arg =
358	    { .name = filename, .replace = &lastdirent/*disk endian */ };
359	/* Search entry that removed. replace it to last dirent. */
360	if ((error = v7fs_datablock_foreach(fs, parent_dir, remove_subr,
361	    &lookup_arg)) != V7FS_ITERATOR_BREAK)
362		return ENOENT;
363
364	/* Contract dirent entries. */
365	v7fs_datablock_contract(fs, parent_dir, sizeof(lastdirent));
366	DPRINTF("done. (dirent size=%dbyte)\n", parent_dir->filesize);
367
368	/* Target inode */
369	if ((error = v7fs_inode_load(fs, &inode, lookup_arg.inode_number)))
370		return error;
371
372	if (v7fs_inode_isdir(&inode)) {
373		parent_dir->nlink--;
374		v7fs_inode_writeback(fs, parent_dir);
375	}
376
377	return 0;
378}
379
380static int
381remove_subr(struct v7fs_self *fs, void *ctx, v7fs_daddr_t blk, size_t sz)
382{
383	struct v7fs_lookup_arg *p = (struct v7fs_lookup_arg *)ctx;
384	struct v7fs_dirent *dir;
385	void *buf;
386	size_t i;
387	int ret = 0;
388
389	DPRINTF("match start blk=%x\n", blk);
390	if (!(buf = scratch_read(fs, blk)))
391		return EIO;
392
393	dir = (struct v7fs_dirent *)buf;
394
395	for (i = 0; i < sz / sizeof(*dir); i++, dir++) {
396		DPRINTF("%d\n", V7FS_VAL16(fs, dir->inode_number));
397		if (strncmp(p->name,
398			(const char *)dir->name, V7FS_NAME_MAX) == 0) {
399			p->inode_number = V7FS_VAL16(fs, dir->inode_number);
400			/* Replace to last dirent. */
401			*dir = *(p->replace); /* disk endian */
402			/* Write back. */
403			if (!fs->io.write(fs->io.cookie, buf, blk))
404				ret = EIO;
405			else
406				ret = V7FS_ITERATOR_BREAK;
407			break;
408		}
409	}
410	scratch_free(fs, buf);
411
412	return ret;
413}
414