1 /*	$NetBSD: msdosfs_denode.c,v 1.7 2015/03/29 05:52:59 agc Exp $	*/
2 
3 /*-
4  * SPDX-License-Identifier: BSD-4-Clause
5  *
6  * Copyright (C) 1994, 1995, 1997 Wolfgang Solfrank.
7  * Copyright (C) 1994, 1995, 1997 TooLs GmbH.
8  * All rights reserved.
9  * Original code by Paul Popelka (paulp@uts.amdahl.com) (see below).
10  *
11  * Redistribution and use in source and binary forms, with or without
12  * modification, are permitted provided that the following conditions
13  * are met:
14  * 1. Redistributions of source code must retain the above copyright
15  *    notice, this list of conditions and the following disclaimer.
16  * 2. Redistributions in binary form must reproduce the above copyright
17  *    notice, this list of conditions and the following disclaimer in the
18  *    documentation and/or other materials provided with the distribution.
19  * 3. All advertising materials mentioning features or use of this software
20  *    must display the following acknowledgement:
21  *	This product includes software developed by TooLs GmbH.
22  * 4. The name of TooLs GmbH may not be used to endorse or promote products
23  *    derived from this software without specific prior written permission.
24  *
25  * THIS SOFTWARE IS PROVIDED BY TOOLS GMBH ``AS IS'' AND ANY EXPRESS OR
26  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
27  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
28  * IN NO EVENT SHALL TOOLS GMBH BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
29  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
30  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
31  * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
32  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
33  * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
34  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
35  */
36 /*-
37  * Written by Paul Popelka (paulp@uts.amdahl.com)
38  *
39  * You can do anything you want with this software, just don't say you wrote
40  * it, and don't remove this notice.
41  *
42  * This software is provided "as is".
43  *
44  * The author supplies this software to be publicly redistributed on the
45  * understanding that the author is not responsible for the correct
46  * functioning of this software in any circumstances and is not liable for
47  * any damages caused by this software.
48  *
49  * October 1992
50  */
51 
52 #include <sys/cdefs.h>
53 __FBSDID("$FreeBSD: stable/12/usr.sbin/makefs/msdos/msdosfs_denode.c 373330 2024-12-12 22:45:14Z jrtc27 $");
54 
55 #include <sys/param.h>
56 #include <sys/errno.h>
57 
58 #include <stdio.h>
59 #include <string.h>
60 #include <stdlib.h>
61 #include <util.h>
62 
63 #include <fs/msdosfs/bpb.h>
64 
65 #include "makefs.h"
66 #include "msdos.h"
67 
68 #include "ffs/buf.h"
69 
70 #include "msdos/denode.h"
71 #include "msdos/direntry.h"
72 #include "msdos/fat.h"
73 #include "msdos/msdosfsmount.h"
74 
75 /*
76  * If deget() succeeds it returns with the gotten denode locked().
77  *
78  * pmp	     - address of msdosfsmount structure of the filesystem containing
79  *	       the denode of interest.  The pm_dev field and the address of
80  *	       the msdosfsmount structure are used.
81  * dirclust  - which cluster bp contains, if dirclust is 0 (root directory)
82  *	       diroffset is relative to the beginning of the root directory,
83  *	       otherwise it is cluster relative.
84  * diroffset - offset past begin of cluster of denode we want
85  * depp	     - returns the address of the gotten denode.
86  */
87 int
deget(struct msdosfsmount * pmp,u_long dirclust,u_long diroffset,struct denode ** depp)88 deget(struct msdosfsmount *pmp, u_long dirclust, u_long diroffset,
89     struct denode **depp)
90 {
91 	int error;
92 	uint64_t inode;
93 	struct direntry *direntptr;
94 	struct denode *ldep;
95 	struct buf *bp;
96 
97 	MSDOSFS_DPRINTF(("deget(pmp %p, dirclust %lu, diroffset %lx, depp %p)\n",
98 	    pmp, dirclust, diroffset, depp));
99 
100 	/*
101 	 * On FAT32 filesystems, root is a (more or less) normal
102 	 * directory
103 	 */
104 	if (FAT32(pmp) && dirclust == MSDOSFSROOT)
105 		dirclust = pmp->pm_rootdirblk;
106 
107 	inode = (uint64_t)pmp->pm_bpcluster * dirclust + diroffset;
108 
109 	ldep = ecalloc(1, sizeof(*ldep));
110 	ldep->de_vnode = NULL;
111 	ldep->de_flag = 0;
112 	ldep->de_dirclust = dirclust;
113 	ldep->de_diroffset = diroffset;
114 	ldep->de_inode = inode;
115 	ldep->de_pmp = pmp;
116 	ldep->de_refcnt = 1;
117 	fc_purge(ldep, 0);	/* init the FAT cache for this denode */
118 	/*
119 	 * Copy the directory entry into the denode area of the vnode.
120 	 */
121 	if ((dirclust == MSDOSFSROOT
122 	     || (FAT32(pmp) && dirclust == pmp->pm_rootdirblk))
123 	    && diroffset == MSDOSFSROOT_OFS) {
124 		/*
125 		 * Directory entry for the root directory. There isn't one,
126 		 * so we manufacture one. We should probably rummage
127 		 * through the root directory and find a label entry (if it
128 		 * exists), and then use the time and date from that entry
129 		 * as the time and date for the root denode.
130 		 */
131 		ldep->de_vnode = (struct vnode *)-1;
132 
133 		ldep->de_Attributes = ATTR_DIRECTORY;
134 		ldep->de_LowerCase = 0;
135 		if (FAT32(pmp))
136 			ldep->de_StartCluster = pmp->pm_rootdirblk;
137 			/* de_FileSize will be filled in further down */
138 		else {
139 			ldep->de_StartCluster = MSDOSFSROOT;
140 			ldep->de_FileSize = pmp->pm_rootdirsize * DEV_BSIZE;
141 		}
142 		/*
143 		 * fill in time and date so that dos2unixtime() doesn't
144 		 * spit up when called from msdosfs_getattr() with root
145 		 * denode
146 		 */
147 		ldep->de_CHun = 0;
148 		ldep->de_CTime = 0x0000;	/* 00:00:00	 */
149 		ldep->de_CDate = (0 << DD_YEAR_SHIFT) | (1 << DD_MONTH_SHIFT)
150 		    | (1 << DD_DAY_SHIFT);
151 		/* Jan 1, 1980	 */
152 		ldep->de_ADate = ldep->de_CDate;
153 		ldep->de_MTime = ldep->de_CTime;
154 		ldep->de_MDate = ldep->de_CDate;
155 		/* leave the other fields as garbage */
156 	} else {
157 		error = readep(pmp, dirclust, diroffset, &bp, &direntptr);
158 		if (error) {
159 			ldep->de_Name[0] = SLOT_DELETED;
160 
161 			*depp = NULL;
162 			return (error);
163 		}
164 		(void)DE_INTERNALIZE(ldep, direntptr);
165 		brelse(bp);
166 	}
167 
168 	/*
169 	 * Fill in a few fields of the vnode and finish filling in the
170 	 * denode.  Then return the address of the found denode.
171 	 */
172 	if (ldep->de_Attributes & ATTR_DIRECTORY) {
173 		/*
174 		 * Since DOS directory entries that describe directories
175 		 * have 0 in the filesize field, we take this opportunity
176 		 * to find out the length of the directory and plug it into
177 		 * the denode structure.
178 		 */
179 		u_long size;
180 
181 		/*
182 		 * XXX it sometimes happens that the "." entry has cluster
183 		 * number 0 when it shouldn't.  Use the actual cluster number
184 		 * instead of what is written in directory entry.
185 		 */
186 		if (diroffset == 0 && ldep->de_StartCluster != dirclust) {
187 			MSDOSFS_DPRINTF(("deget(): \".\" entry at clust %lu != %lu\n",
188 			    dirclust, ldep->de_StartCluster));
189 
190 			ldep->de_StartCluster = dirclust;
191 		}
192 
193 		if (ldep->de_StartCluster != MSDOSFSROOT) {
194 			error = pcbmap(ldep, 0xffff, 0, &size, 0);
195 			if (error == E2BIG) {
196 				ldep->de_FileSize = de_cn2off(pmp, size);
197 				error = 0;
198 			} else {
199 				MSDOSFS_DPRINTF(("deget(): pcbmap returned %d\n",
200 				    error));
201 			}
202 		}
203 	}
204 	*depp = ldep;
205 	return (0);
206 }
207 
208 /*
209  * Truncate the file described by dep to the length specified by length.
210  */
211 int
detrunc(struct denode * dep,u_long length,int flags)212 detrunc(struct denode *dep, u_long length, int flags)
213 {
214 	int error;
215 	int allerror;
216 	u_long eofentry;
217 	u_long chaintofree;
218 	daddr_t bn;
219 	int boff;
220 	int isadir = dep->de_Attributes & ATTR_DIRECTORY;
221 	struct buf *bp;
222 	struct msdosfsmount *pmp = dep->de_pmp;
223 
224 	MSDOSFS_DPRINTF(("detrunc(): file %s, length %lu, flags %x\n",
225 	    dep->de_Name, length, flags));
226 
227 	/*
228 	 * Disallow attempts to truncate the root directory since it is of
229 	 * fixed size.  That's just the way dos filesystems are.  We use
230 	 * the VROOT bit in the vnode because checking for the directory
231 	 * bit and a startcluster of 0 in the denode is not adequate to
232 	 * recognize the root directory at this point in a file or
233 	 * directory's life.
234 	 */
235 	if (dep->de_vnode != NULL && !FAT32(pmp)) {
236 		MSDOSFS_DPRINTF(("detrunc(): can't truncate root directory, "
237 		    "clust %ld, offset %ld\n",
238 		    dep->de_dirclust, dep->de_diroffset));
239 
240 		return (EINVAL);
241 	}
242 
243 	if (dep->de_FileSize < length)
244 		return deextend(dep, length);
245 
246 	/*
247 	 * If the desired length is 0 then remember the starting cluster of
248 	 * the file and set the StartCluster field in the directory entry
249 	 * to 0.  If the desired length is not zero, then get the number of
250 	 * the last cluster in the shortened file.  Then get the number of
251 	 * the first cluster in the part of the file that is to be freed.
252 	 * Then set the next cluster pointer in the last cluster of the
253 	 * file to CLUST_EOFE.
254 	 */
255 	if (length == 0) {
256 		chaintofree = dep->de_StartCluster;
257 		dep->de_StartCluster = 0;
258 		eofentry = ~0;
259 	} else {
260 		error = pcbmap(dep, de_clcount(pmp, length) - 1, 0,
261 		    &eofentry, 0);
262 		if (error) {
263 			MSDOSFS_DPRINTF(("detrunc(): pcbmap fails %d\n",
264 			    error));
265 			return (error);
266 		}
267 	}
268 
269 	fc_purge(dep, de_clcount(pmp, length));
270 
271 	/*
272 	 * If the new length is not a multiple of the cluster size then we
273 	 * must zero the tail end of the new last cluster in case it
274 	 * becomes part of the file again because of a seek.
275 	 */
276 	if ((boff = length & pmp->pm_crbomask) != 0) {
277 		if (isadir) {
278 			bn = cntobn(pmp, eofentry);
279 			error = bread(pmp->pm_devvp, bn, pmp->pm_bpcluster,
280 			    0, &bp);
281 			if (error) {
282 				brelse(bp);
283 				MSDOSFS_DPRINTF(("detrunc(): bread fails %d\n",
284 				    error));
285 
286 				return (error);
287 			}
288 			memset(bp->b_data + boff, 0, pmp->pm_bpcluster - boff);
289 				bwrite(bp);
290 		}
291 	}
292 
293 	/*
294 	 * Write out the updated directory entry.  Even if the update fails
295 	 * we free the trailing clusters.
296 	 */
297 	dep->de_FileSize = length;
298 	if (!isadir)
299 		dep->de_flag |= DE_UPDATE|DE_MODIFIED;
300 	MSDOSFS_DPRINTF(("detrunc(): allerror %d, eofentry %lu\n",
301 	    allerror, eofentry));
302 
303 	/*
304 	 * If we need to break the cluster chain for the file then do it
305 	 * now.
306 	 */
307 	if (eofentry != ~0) {
308 		error = fatentry(FAT_GET_AND_SET, pmp, eofentry,
309 				 &chaintofree, CLUST_EOFE);
310 		if (error) {
311 			MSDOSFS_DPRINTF(("detrunc(): fatentry errors %d\n",
312 			    error));
313 			return (error);
314 		}
315 		fc_setcache(dep, FC_LASTFC, de_cluster(pmp, length - 1),
316 		    eofentry);
317 	}
318 
319 	/*
320 	 * Now free the clusters removed from the file because of the
321 	 * truncation.
322 	 */
323 	if (chaintofree != 0 && !MSDOSFSEOF(pmp, chaintofree))
324 		freeclusterchain(pmp, chaintofree);
325 
326 	return (allerror);
327 }
328 
329 /*
330  * Extend the file described by dep to length specified by length.
331  */
332 int
deextend(struct denode * dep,u_long length)333 deextend(struct denode *dep, u_long length)
334 {
335 	struct msdosfsmount *pmp = dep->de_pmp;
336 	u_long count;
337 	int error;
338 
339 	/*
340 	 * The root of a DOS filesystem cannot be extended.
341 	 */
342 	if (dep->de_vnode != NULL && !FAT32(pmp))
343 		return (EINVAL);
344 
345 	/*
346 	 * Directories cannot be extended.
347 	 */
348 	if (dep->de_Attributes & ATTR_DIRECTORY)
349 		return (EISDIR);
350 
351 	if (length <= dep->de_FileSize)
352 		return (E2BIG);
353 
354 	/*
355 	 * Compute the number of clusters to allocate.
356 	 */
357 	count = de_clcount(pmp, length) - de_clcount(pmp, dep->de_FileSize);
358 	if (count > 0) {
359 		if (count > pmp->pm_freeclustercount)
360 			return (ENOSPC);
361 		error = extendfile(dep, count, NULL, NULL, DE_CLEAR);
362 		if (error) {
363 			/* truncate the added clusters away again */
364 			(void) detrunc(dep, dep->de_FileSize, 0);
365 			return (error);
366 		}
367 	}
368 
369 	/*
370 	 * Zero extend file range; ubc_zerorange() uses ubc_alloc() and a
371 	 * memset(); we set the write size so ubc won't read in file data that
372 	 * is zero'd later.
373 	 */
374 	dep->de_FileSize = length;
375 	dep->de_flag |= DE_UPDATE | DE_MODIFIED;
376 	return 0;
377 }
378