xref: /freebsd/usr.sbin/makefs/msdos/msdosfs_denode.c (revision e12ff891366cf94db4bfe4c2c810b26a5531053d)
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$");
54 
55 #include <sys/param.h>
56 #include <sys/errno.h>
57 #include <sys/vnode.h>
58 
59 #include <stdbool.h>
60 #include <stdio.h>
61 #include <string.h>
62 #include <stdlib.h>
63 #include <util.h>
64 
65 #include "ffs/buf.h"
66 
67 #include <fs/msdosfs/bpb.h>
68 #include <fs/msdosfs/direntry.h>
69 #include <fs/msdosfs/denode.h>
70 #include <fs/msdosfs/fat.h>
71 #include <fs/msdosfs/msdosfsmount.h>
72 
73 #include "makefs.h"
74 #include "msdos.h"
75 
76 
77 /*
78  * If deget() succeeds it returns with the gotten denode locked().
79  *
80  * pmp	     - address of msdosfsmount structure of the filesystem containing
81  *	       the denode of interest.  The pm_dev field and the address of
82  *	       the msdosfsmount structure are used.
83  * dirclust  - which cluster bp contains, if dirclust is 0 (root directory)
84  *	       diroffset is relative to the beginning of the root directory,
85  *	       otherwise it is cluster relative.
86  * diroffset - offset past begin of cluster of denode we want
87  * depp	     - returns the address of the gotten denode.
88  */
89 int
90 deget(struct msdosfsmount *pmp, u_long dirclust, u_long diroffset,
91     struct denode **depp)
92 {
93 	int error;
94 	uint64_t inode;
95 	struct direntry *direntptr;
96 	struct denode *ldep;
97 	struct buf *bp;
98 
99 	MSDOSFS_DPRINTF(("deget(pmp %p, dirclust %lu, diroffset %lx, depp %p)\n",
100 	    pmp, dirclust, diroffset, depp));
101 
102 	/*
103 	 * On FAT32 filesystems, root is a (more or less) normal
104 	 * directory
105 	 */
106 	if (FAT32(pmp) && dirclust == MSDOSFSROOT)
107 		dirclust = pmp->pm_rootdirblk;
108 
109 	inode = (uint64_t)pmp->pm_bpcluster * dirclust + diroffset;
110 
111 	ldep = ecalloc(1, sizeof(*ldep));
112 	ldep->de_vnode = NULL;
113 	ldep->de_flag = 0;
114 	ldep->de_dirclust = dirclust;
115 	ldep->de_diroffset = diroffset;
116 	ldep->de_inode = inode;
117 	ldep->de_pmp = pmp;
118 	ldep->de_refcnt = 1;
119 	fc_purge(ldep, 0);	/* init the FAT cache for this denode */
120 	/*
121 	 * Copy the directory entry into the denode area of the vnode.
122 	 */
123 	if ((dirclust == MSDOSFSROOT
124 	     || (FAT32(pmp) && dirclust == pmp->pm_rootdirblk))
125 	    && diroffset == MSDOSFSROOT_OFS) {
126 		/*
127 		 * Directory entry for the root directory. There isn't one,
128 		 * so we manufacture one. We should probably rummage
129 		 * through the root directory and find a label entry (if it
130 		 * exists), and then use the time and date from that entry
131 		 * as the time and date for the root denode.
132 		 */
133 		ldep->de_vnode = (struct vnode *)-1;
134 
135 		ldep->de_Attributes = ATTR_DIRECTORY;
136 		ldep->de_LowerCase = 0;
137 		if (FAT32(pmp))
138 			ldep->de_StartCluster = pmp->pm_rootdirblk;
139 			/* de_FileSize will be filled in further down */
140 		else {
141 			ldep->de_StartCluster = MSDOSFSROOT;
142 			ldep->de_FileSize = pmp->pm_rootdirsize * DEV_BSIZE;
143 		}
144 		/*
145 		 * fill in time and date so that dos2unixtime() doesn't
146 		 * spit up when called from msdosfs_getattr() with root
147 		 * denode
148 		 */
149 		ldep->de_CHun = 0;
150 		ldep->de_CTime = 0x0000;	/* 00:00:00	 */
151 		ldep->de_CDate = (0 << DD_YEAR_SHIFT) | (1 << DD_MONTH_SHIFT)
152 		    | (1 << DD_DAY_SHIFT);
153 		/* Jan 1, 1980	 */
154 		ldep->de_ADate = ldep->de_CDate;
155 		ldep->de_MTime = ldep->de_CTime;
156 		ldep->de_MDate = ldep->de_CDate;
157 		/* leave the other fields as garbage */
158 	} else {
159 		error = readep(pmp, dirclust, diroffset, &bp, &direntptr);
160 		if (error) {
161 			ldep->de_Name[0] = SLOT_DELETED;
162 
163 			*depp = NULL;
164 			return (error);
165 		}
166 		(void)DE_INTERNALIZE(ldep, direntptr);
167 		brelse(bp);
168 	}
169 
170 	/*
171 	 * Fill in a few fields of the vnode and finish filling in the
172 	 * denode.  Then return the address of the found denode.
173 	 */
174 	if (ldep->de_Attributes & ATTR_DIRECTORY) {
175 		/*
176 		 * Since DOS directory entries that describe directories
177 		 * have 0 in the filesize field, we take this opportunity
178 		 * to find out the length of the directory and plug it into
179 		 * the denode structure.
180 		 */
181 		u_long size;
182 
183 		/*
184 		 * XXX it sometimes happens that the "." entry has cluster
185 		 * number 0 when it shouldn't.  Use the actual cluster number
186 		 * instead of what is written in directory entry.
187 		 */
188 		if (diroffset == 0 && ldep->de_StartCluster != dirclust) {
189 			MSDOSFS_DPRINTF(("deget(): \".\" entry at clust %lu != %lu\n",
190 			    dirclust, ldep->de_StartCluster));
191 
192 			ldep->de_StartCluster = dirclust;
193 		}
194 
195 		if (ldep->de_StartCluster != MSDOSFSROOT) {
196 			error = pcbmap(ldep, 0xffff, 0, &size, 0);
197 			if (error == E2BIG) {
198 				ldep->de_FileSize = de_cn2off(pmp, size);
199 				error = 0;
200 			} else {
201 				MSDOSFS_DPRINTF(("deget(): pcbmap returned %d\n",
202 				    error));
203 			}
204 		}
205 	}
206 	*depp = ldep;
207 	return (0);
208 }
209 
210 /*
211  * Truncate the file described by dep to the length specified by length.
212  */
213 int
214 detrunc(struct denode *dep, u_long length, int flags, struct ucred *cred)
215 {
216 	int error;
217 	int allerror;
218 	u_long eofentry;
219 	u_long chaintofree;
220 	daddr_t bn;
221 	int boff;
222 	int isadir = dep->de_Attributes & ATTR_DIRECTORY;
223 	struct buf *bp;
224 	struct msdosfsmount *pmp = dep->de_pmp;
225 
226 	MSDOSFS_DPRINTF(("detrunc(): file %s, length %lu, flags %x\n",
227 	    dep->de_Name, length, flags));
228 
229 	/*
230 	 * Disallow attempts to truncate the root directory since it is of
231 	 * fixed size.  That's just the way dos filesystems are.  We use
232 	 * the VROOT bit in the vnode because checking for the directory
233 	 * bit and a startcluster of 0 in the denode is not adequate to
234 	 * recognize the root directory at this point in a file or
235 	 * directory's life.
236 	 */
237 	if (dep->de_vnode != NULL && !FAT32(pmp)) {
238 		MSDOSFS_DPRINTF(("detrunc(): can't truncate root directory, "
239 		    "clust %ld, offset %ld\n",
240 		    dep->de_dirclust, dep->de_diroffset));
241 
242 		return (EINVAL);
243 	}
244 
245 	if (dep->de_FileSize < length)
246 		return deextend(dep, length, cred);
247 
248 	/*
249 	 * If the desired length is 0 then remember the starting cluster of
250 	 * the file and set the StartCluster field in the directory entry
251 	 * to 0.  If the desired length is not zero, then get the number of
252 	 * the last cluster in the shortened file.  Then get the number of
253 	 * the first cluster in the part of the file that is to be freed.
254 	 * Then set the next cluster pointer in the last cluster of the
255 	 * file to CLUST_EOFE.
256 	 */
257 	if (length == 0) {
258 		chaintofree = dep->de_StartCluster;
259 		dep->de_StartCluster = 0;
260 		eofentry = ~0;
261 	} else {
262 		error = pcbmap(dep, de_clcount(pmp, length) - 1, 0,
263 		    &eofentry, 0);
264 		if (error) {
265 			MSDOSFS_DPRINTF(("detrunc(): pcbmap fails %d\n",
266 			    error));
267 			return (error);
268 		}
269 	}
270 
271 	fc_purge(dep, de_clcount(pmp, length));
272 
273 	/*
274 	 * If the new length is not a multiple of the cluster size then we
275 	 * must zero the tail end of the new last cluster in case it
276 	 * becomes part of the file again because of a seek.
277 	 */
278 	if ((boff = length & pmp->pm_crbomask) != 0) {
279 		if (isadir) {
280 			bn = cntobn(pmp, eofentry);
281 			error = bread(pmp->pm_devvp, bn, pmp->pm_bpcluster,
282 			    0, &bp);
283 			if (error) {
284 				brelse(bp);
285 				MSDOSFS_DPRINTF(("detrunc(): bread fails %d\n",
286 				    error));
287 
288 				return (error);
289 			}
290 			memset(bp->b_data + boff, 0, pmp->pm_bpcluster - boff);
291 			if (flags & IO_SYNC)
292 				bwrite(bp);
293 			else
294 				bdwrite(bp);
295 		}
296 	}
297 
298 	/*
299 	 * Write out the updated directory entry.  Even if the update fails
300 	 * we free the trailing clusters.
301 	 */
302 	dep->de_FileSize = length;
303 	if (!isadir)
304 		dep->de_flag |= DE_UPDATE|DE_MODIFIED;
305 	MSDOSFS_DPRINTF(("detrunc(): allerror %d, eofentry %lu\n",
306 	    allerror, eofentry));
307 
308 	/*
309 	 * If we need to break the cluster chain for the file then do it
310 	 * now.
311 	 */
312 	if (eofentry != ~0) {
313 		error = fatentry(FAT_GET_AND_SET, pmp, eofentry,
314 				 &chaintofree, CLUST_EOFE);
315 		if (error) {
316 			MSDOSFS_DPRINTF(("detrunc(): fatentry errors %d\n",
317 			    error));
318 			return (error);
319 		}
320 		fc_setcache(dep, FC_LASTFC, de_cluster(pmp, length - 1),
321 		    eofentry);
322 	}
323 
324 	/*
325 	 * Now free the clusters removed from the file because of the
326 	 * truncation.
327 	 */
328 	if (chaintofree != 0 && !MSDOSFSEOF(pmp, chaintofree))
329 		freeclusterchain(pmp, chaintofree);
330 
331 	return (allerror);
332 }
333 
334 /*
335  * Extend the file described by dep to length specified by length.
336  */
337 int
338 deextend(struct denode *dep, u_long length, struct ucred *cred)
339 {
340 	struct msdosfsmount *pmp = dep->de_pmp;
341 	u_long count;
342 	int error;
343 
344 	/*
345 	 * The root of a DOS filesystem cannot be extended.
346 	 */
347 	if (dep->de_vnode != NULL && !FAT32(pmp))
348 		return (EINVAL);
349 
350 	/*
351 	 * Directories cannot be extended.
352 	 */
353 	if (dep->de_Attributes & ATTR_DIRECTORY)
354 		return (EISDIR);
355 
356 	if (length <= dep->de_FileSize)
357 		return (E2BIG);
358 
359 	/*
360 	 * Compute the number of clusters to allocate.
361 	 */
362 	count = de_clcount(pmp, length) - de_clcount(pmp, dep->de_FileSize);
363 	if (count > 0) {
364 		if (count > pmp->pm_freeclustercount)
365 			return (ENOSPC);
366 		error = extendfile(dep, count, NULL, NULL, DE_CLEAR);
367 		if (error) {
368 			/* truncate the added clusters away again */
369 			(void) detrunc(dep, dep->de_FileSize, 0, cred);
370 			return (error);
371 		}
372 	}
373 
374 	/*
375 	 * Zero extend file range; ubc_zerorange() uses ubc_alloc() and a
376 	 * memset(); we set the write size so ubc won't read in file data that
377 	 * is zero'd later.
378 	 */
379 	dep->de_FileSize = length;
380 	dep->de_flag |= DE_UPDATE | DE_MODIFIED;
381 	return 0;
382 }
383