summaryrefslogtreecommitdiff
path: root/usr/src/uts/common/sys/fs/pc_dir.h
blob: 62e01a9f8ae595cd934deaf7086441753f8b4f37 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#ifndef	_SYS_FS_PC_DIR_H
#define	_SYS_FS_PC_DIR_H

#pragma ident	"%Z%%M%	%I%	%E% SMI"

#include <sys/dirent.h>

#ifdef	__cplusplus
extern "C" {
#endif

#define	PCFNAMESIZE	8
#define	PCFEXTSIZE	3
#define	PCMAXNAMLEN	255
#define	PCMAXNAM_UTF16	(256 * sizeof (uint16_t))	/* for UTF-16 */
#define	PCLFNCHUNKSIZE	13

struct pctime {
	ushort_t pct_time;		/* hh:mm:ss (little endian) */
	ushort_t pct_date;		/* yy:mm:dd (little endian) */
};

/*
 * Shifts and masks for time and date fields, in host byte order.
 */
#define	SECSHIFT	0
#define	SECMASK		0x1F
#define	MINSHIFT	5
#define	MINMASK		0x3F
#define	HOURSHIFT	11
#define	HOURMASK	0x1F

#define	DAYSHIFT	0
#define	DAYMASK		0x1F
#define	MONSHIFT	5
#define	MONMASK		0x0F
#define	YEARSHIFT	9
#define	YEARMASK	0x7F

struct pcdir {
	char pcd_filename[PCFNAMESIZE];	/* file name */
	char pcd_ext[PCFEXTSIZE];	/* file extension */
	uchar_t	pcd_attr;		/* file attributes */
	uchar_t	pcd_ntattr;		/* reserved for NT attributes */
	uchar_t	pcd_crtime_msec;	/* milliseconds after the minute */
	struct pctime pcd_crtime;	/* creation time/date */
	ushort_t pcd_ladate;		/* last-access date */
	union {
		uint16_t pcd_eattr;	/* OS/2 extended attribute */
		pc_cluster16_t pcd_scluster_hi;
	} un;
	struct pctime pcd_mtime;	/* last modified time/date */
	pc_cluster16_t pcd_scluster_lo;	/* starting cluster (little endian) */
	uint_t	pcd_size;		/* file size (little endian) */
};

#ifdef	__cplusplus
}
#endif

#include <sys/fs/pc_node.h>

#ifdef	__cplusplus
extern "C" {
#endif

/*
 * Long filename support (introduced by Windows 95) is an interesting
 * exercise in compatibility. Basically, it is now no longer the case
 * that an entry in a directory (as described by the 'pcdir' structure above)
 * contains the entire name of a file. Now, a directory entry can consist of
 * a long filename component (a series of 'pcdir'-like structures) followed
 * by a short filename (the old form). Each long filename component is
 * identified by having it's Read-Only, Hidden, System, and Volume Label
 * attributes set.  Each can store 13 Unicode characters (16-bits, of
 * which we only look at the lower 8 for now), broken into (gak) three
 * sections of the entry (since that's the way the available bits fall out).
 * In addition, each long filename entry has a sequence number (starting
 * from 1). The first entry has bit 7 (0x40) set in the sequence number, and
 * has the maximum value in the sequence. This may seem a bit backwards, and
 * it gets worse: the first entry stores the last component of
 * the name. So the directory entry for a file named
 * "This is a very long filename indeed" might look like this:
 *
 * Offset  Sequence      Component         Attributes    Cluster    Size
 *    0      0x43       "me indeed"          RSHV           0         0
 *   32      0x02       "y long filena"      RSHV           0         0
 *   64      0x01       "This is a ver"      RSHV           0         0
 *   96      ----       "THISIS~1.TXT"       <whatever>  2122       110
 *
 * The last entry is for the short filename, which stores actual information
 * about the file (like type, permissions, cluster, and size). The short name
 * is also used by non-long-filename aware applications (like Windows 3.X and
 * DOS). This name is generated by Windows 95 (and now Solaris) from the
 * long name, and must (of course) be unique within the directory.
 * Solaris continues to this entry to actually identify the file and its
 * attributes (filenames only really matter when names are used, like at
 * lookup/readdir/remove/create/rename time - for general access to the file
 * they aren't used).
 *
 * Long filenames can also be broken by applications that don't
 * understand them (for example, a Solaris 2.5.1 user could rename
 * "THISIS~1.TXT" to "test.exe"). This can be detected because each long
 * filename component has a checksum which is based on the short filename.
 * After reading the long filename entry, if the checksum doesn't match the
 * short name that follows, we simply ignore it and use the short name.
 *
 * One subtle thing - though long file names are case-sensitive,
 * searches for them are not.
 *
 * Another _very_ subtle thing. The number of characters in the
 * last long filename chunk (the first entry, with the 0x40 bit set) is
 * either all the characters (if there is no null, '\0'), or all the
 * characters up to the null. _However_, if the remaining characters are
 * null, Norton Disk Doctor and Microsoft ScanDisk will claim
 * that the filename entry is damaged. The remaining bytes must actually
 * contain 0xff (discovered with Disk Doctor).
 *
 * Some information about long filename support can be found in the
 * book "Inside Windows 95" by Adrian King.
 */

/*
 * The number of bytes in each section of the name in a long filename
 * entry. This is _bytes_, not characters: each character is actually
 * 16 bits.
 */
#define	PCLF_FIRSTNAMESIZE	10
#define	PCLF_SECONDNAMESIZE	12
#define	PCLF_THIRDNAMESIZE	4

/*
 * A long filename entry. It must match the 'pcdir' structure in size,
 * and pcdl_attr must overlap pcd_attr.
 */
struct pcdir_lfn {
	uchar_t pcdl_ordinal;	/* lfn order. First is 01, next 02, */
				/* last has bit 7 (0x40) set */
	uchar_t pcdl_firstfilename[PCLF_FIRSTNAMESIZE];
	uchar_t pcdl_attr;
	uchar_t pcdl_type;	/* type - always contains 0 for an LFN entry */
	uchar_t pcdl_checksum;	/* checksum to validate the LFN entry - */
				/* based on the short name */
	uchar_t pcdl_secondfilename[PCLF_SECONDNAMESIZE];
	pc_cluster16_t pcd_scluster;	/* (not used, always 0) */
	uchar_t pcdl_thirdfilename[PCLF_THIRDNAMESIZE];
};

/*
 * FAT LFN entries are consecutively numbered downwards, and the last
 * entry of a LFN chain will have the 0x40 'termination' marker logically
 * or'ed in. The entry immediately preceeding the short name has number 1,
 * consecutively increasing. Since the filename length limit on FAT is
 * 255 unicode characters and every LFN entry contributes 13 characters,
 * the maximum sequence number is 255/13 + 1 == 20.
 */
#define	PCDL_IS_LAST_LFN(x) ((x->pcdl_ordinal) & 0x40)
#define	PCDL_LFN_BITS (PCA_RDONLY | PCA_HIDDEN | PCA_SYSTEM | PCA_LABEL)
#define	PCDL_LFN_MASK (PCDL_LFN_BITS | PCA_DIR | PCA_ARCH)
#define	PCDL_LFN_VALID_ORD(x)						\
	(((((struct pcdir_lfn *)(x))->pcdl_ordinal & ~0x40) > 0) &&	\
	((((struct pcdir_lfn *)(x))->pcdl_ordinal & ~0x40) <= 20))
#define	PCDL_IS_LFN(x)							\
	(enable_long_filenames &&					\
	(((x)->pcd_attr & PCDL_LFN_MASK) == PCDL_LFN_BITS) &&		\
	PCDL_LFN_VALID_ORD((x)))

/*
 * The first char of the file name has special meaning as follows:
 */
#define	PCD_UNUSED	((char)0x00)	/* entry has never been used */
#define	PCD_ERASED	((char)0xE5)	/* entry was erased */

/*
 * File attributes.
 */
#define	PCA_RDONLY	0x01	/* file is read only */
#define	PCA_HIDDEN	0x02	/* file is hidden */
#define	PCA_SYSTEM	0x04	/* system file */
#define	PCA_LABEL	0x08	/* entry contains the volume label */
#define	PCA_DIR		0x10	/* subdirectory */
#define	PCA_ARCH	0x20	/* file has been modified since last backup */

/*
 * Avoid hidden files unless the private variable is set.
 * Always avoid the label.
 */
#define	PCA_IS_HIDDEN(fsp, attr) \
	((((attr) & PCA_LABEL) == PCA_LABEL) || \
	((((fsp)->pcfs_flags & PCFS_HIDDEN) == 0) && \
	    ((attr) & (PCA_HIDDEN | PCA_SYSTEM))))

#define	PC_NAME_IS_DOT(namep) \
	(((namep)[0] == '.') && ((namep)[1] == '\0'))
#define	PC_NAME_IS_DOTDOT(namep) \
	(((namep)[0] == '.') && ((namep)[1] == '.') && ((namep)[2] == '\0'))
#define	PC_SHORTNAME_IS_DOT(namep) \
	(((namep)[0] == '.') && ((namep)[1] == ' '))
#define	PC_SHORTNAME_IS_DOTDOT(namep) \
	(((namep)[0] == '.') && ((namep)[1] == '.') && ((namep)[2] == ' '))
/*
 * slot structure is used by the directory search routine to return
 * the results of the search.  If the search is successful sl_blkno and
 * sl_offset reflect the disk address of the entry and sl_ep points to
 * the actual entry data in buffer sl_bp. sl_flags is set to whether the
 * entry is dot or dotdot. If the search is unsuccessful sl_blkno and
 * sl_offset points to an empty directory slot if there are any. Otherwise
 * it is set to -1.
 */
struct pcslot {
	enum {SL_NONE, SL_FOUND, SL_EXTEND} sl_status;	/* slot status */
	daddr_t		sl_blkno;	/* disk block number which has entry */
	int		sl_offset;	/* offset of entry within block */
	struct buf	*sl_bp;		/* buffer containing entry data */
	struct pcdir	*sl_ep;		/* pointer to entry data */
	int		sl_flags;	/* flags (see below) */
};
#define	SL_DOT		1	/* entry point to self */
#define	SL_DOTDOT	2	/* entry points to parent */

/*
 * A pcfs directory entry. Directory entries are actually variable
 * length, but this is the maximum size.
 *
 * This _must_ match a dirent64 structure in format.
 * d_name is 512 bytes long to accomodate 256 UTF-16 characters.
 */
struct pc_dirent {
	ino64_t		d_ino;		/* "inode number" of entry */
	off64_t		d_off;		/* offset of disk directory entry */
	unsigned short	d_reclen;	/* length of this record */
	char		d_name[PCMAXNAM_UTF16];
};

/*
 * Check FAT 8.3 filename characters for validity.
 * Lacking a kernel iconv, codepage support for short filenames
 * is not provided.
 * Short names must be uppercase ASCII (no support for MSDOS
 * codepages right now, sorry) and may not contain any of
 * *+=|\[];:",<>.?/ which are explicitly forbidden by the
 * FAT specifications.
 */
#define	pc_invalchar(c)						\
	(((c) >= 'a' && (c) <= 'z') ||				\
	(c) == '"' || (c) == '*' || (c) == '+' || (c) == ',' || \
	(c) == '.' || (c) == '/' || (c) == ':' || (c) == ';' || \
	(c) == '<' || (c) == '=' || (c) == '>' || (c) == '?' || \
	(c) == '[' || (c) == '|' || (c) == ']' || (c) == '\\')

#define	pc_validchar(c)	(((c) >= ' ' && !((c) & ~0177)) && !pc_invalchar(c))


#ifdef _KERNEL

/*
 * macros for converting ASCII to/from upper or lower case.
 * users may give and get names in lower case, but they are stored on the
 * disk in upper case to be PCDOS compatible.
 * These would better come from some shared source in <sys/...> but
 * there is no such place yet.
 */
#define	toupper(C)	(((C) >= 'a' && (C) <= 'z') ? (C) - 'a' + 'A' : (C))
#define	tolower(C)	(((C) >= 'A' && (C) <= 'Z') ? (C) - 'A' + 'a' : (C))

extern int pc_tvtopct(timestruc_t *, struct pctime *);	/* timeval to pctime */
extern void pc_pcttotv(struct pctime *, int64_t *);	/* pctime to timeval */
extern int pc_valid_lfn_char(char);		/* valid long filename ch */

extern int pc_read_long_fn(struct vnode *, struct uio *,
    struct pc_dirent *, struct pcdir **, offset_t *, struct buf **);
extern int pc_read_short_fn(struct vnode *, struct uio *,
    struct pc_dirent *, struct pcdir **, offset_t *, struct buf **);
extern int pc_match_long_fn(struct pcnode *, char *, struct pcdir **,
    struct pcslot *, offset_t *);
extern int pc_match_short_fn(struct pcnode *, char *,
    struct pcdir **, struct pcslot *, offset_t *);
extern uchar_t pc_checksum_long_fn(char *, char *);
extern void set_long_fn_chunk(struct pcdir_lfn *, char *, int);
extern int pc_valid_long_fn(char *, int);
extern int pc_extract_long_fn(struct pcnode *, char *,
    struct pcdir **, offset_t *offset, struct buf **);
extern int pc_fname_ext_to_name(char *, char *, char *, int);

extern pc_cluster32_t pc_getstartcluster(struct pcfs *, struct pcdir *);
extern void pc_setstartcluster(struct pcfs *, struct pcdir *, pc_cluster32_t);

/*
 * Private tunables
 */

/*
 * Use long filenames (Windows 95). Disabling this causes pcfs
 * to not recognize long filenames at all, which may cause it to
 * break associations between the short and long names. This is likely
 * to leave unused long filename entries  in directories (which may make
 * apparently empty directories unremovable), and would require a fsck_pcfs
 * to find and fix (or a Windows utility like Norton Disk Doctor or
 * Microsoft ScanDisk).
 */
extern int enable_long_filenames;	/* default: on */

#endif

#ifdef	__cplusplus
}
#endif

#endif	/* _SYS_FS_PC_DIR_H */