* Copyright 1999-2001, Be Incorporated. All Rights Reserved.
* Copyright 2001-2020, Axel Dörfler, axeld@pinc-software.de.
* Copyright 2024, Haiku, Inc. All rights reserved.
* This file may be used under the terms of the Be Sample Code License.
*/
* SPDX-License-Identifier: BSD-4-Clause
*
* Copyright (C) 1994, 1995, 1997 Wolfgang Solfrank.
* Copyright (C) 1994, 1995, 1997 TooLs GmbH.
* All rights reserved.
* Original code by Paul Popelka (paulp@uts.amdahl.com) (see below).
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by TooLs GmbH.
* 4. The name of TooLs GmbH may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY TOOLS GMBH ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL TOOLS GMBH BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
* Written by Paul Popelka (paulp@uts.amdahl.com)
*
* You can do anything you want with this software, just don't say you wrote
* it, and don't remove this notice.
*
* This software is provided "as is".
*
* The author supplies this software to be publicly redistributed on the
* understanding that the author is not responsible for the correct
* functioning of this software in any circumstances and is not liable for
* any damages caused by this software.
*
* October 1992
*/
#include "support.h"
#ifndef FS_SHELL
#include <dirent.h>
#include <malloc.h>
#include <NodeMonitor.h>
#include <SupportDefs.h>
#include <AutoDeleter.h>
#include <file_systems/mime_ext_table.h>
#include <kernel.h>
#include <real_time_clock.h>
#include <util/AutoLock.h>
#else
#include <fssh_kernel_priv.h>
#endif
#include "debug.h"
#include "dosfs.h"
#include "vcache.h"
extern struct iconv_functions* msdosfs_iconv;
struct emptyDir gDirTemplate = {
{
{'.', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '},
ATTR_DIRECTORY,
0,
0, {0, 0}, {0, 0},
{0, 0},
{0, 0},
{210, 4}, {210, 4},
{0, 0},
{0, 0, 0, 0}
},
{
{'.', '.', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '},
ATTR_DIRECTORY,
0,
0, {0, 0}, {0, 0},
{0, 0},
{0, 0},
{210, 4}, {210, 4},
{0, 0},
{0, 0, 0, 0}
}};
ComponentName::ComponentName(u_int64_t flags, ucred* cred, enum nameiop nameiop, int lkflags,
const char* nameptr)
{
fData.cn_flags = flags;
fData.cn_cred = cred;
fData.cn_nameiop = nameiop;
fData.cn_lkflags = lkflags;
fData.cn_nameptr = strdup(nameptr);
fData.cn_namelen = strlen(fData.cn_nameptr);
}
ComponentName::~ComponentName()
{
free(fData.cn_nameptr);
}
struct componentname*
ComponentName::Data()
{
return &fData;
}
bool
is_filename_legal(const char* name)
{
if (name == NULL)
return false;
const char illegal[] = "\\/:*?\"<>|";
uint32 len = strlen(name);
if (len <= 0)
return false;
if (strrchr(name, '.') == name)
return false;
bool hasContent = false;
for (uint32 i = 0; i < len && hasContent == false; i++) {
if (name[i] != ' ' && name[i] != '.')
hasContent = true;
}
if (hasContent == false)
return false;
for (uint32 i = 0; i < len; i++) {
if (name[i] & 0x80)
continue;
if (strchr(illegal, name[i]))
return false;
if (static_cast<unsigned char>(name[i]) < 32)
return false;
}
return true;
}
bool
is_shortname_legal(const u_char* name)
{
const char* device_names[] = {"CON ", "PRN ", "AUX ", "NUL ",
"COM0 ", "COM1 ", "COM2 ", "COM3 ", "COM4 ", "COM5 ",
"COM6 ", "COM7 ", "COM8 ", "COM9 ", "LPT0 ", "LPT1 ",
"LPT2 ", "LPT3 ", "LPT4 ", "LPT5 ", "LPT6 ", "LPT7 ",
"LPT8 ", "LPT9 ", NULL};
for (uint32 i = 0; device_names[i]; i++) {
if (memcmp(name, device_names[i], 8) == 0)
return false;
}
return true;
}
@param label A C string of size LABEL_CSTRING.
@post The first LABEL_LENGTH characters of label contain an all-caps space-padded label, or
an error is returned.
*/
status_t
label_to_fat(char* label)
{
if (label[0] == 0) {
INFORM("label_to_fat: empty name\n");
return B_BAD_VALUE;
}
if (label[0] == ' ') {
INFORM("label_to_fat: vol name starts with space\n");
return B_BAD_VALUE;
}
uint32 length = strlen(label);
if (length > LABEL_LENGTH)
return B_NAME_TOO_LONG;
for (uint32 i = 0; i < length; i++) {
char c = label[i];
if (c >= 'a' && c <= 'z')
c+= 'A' - 'a';
if (strchr(sAcceptable, c) || c == ' ') {
label[i] = c;
} else {
INFORM("label_to_fat: vol name contains illegal char (%c at index %" B_PRIu32
")\n", label[i], i);
return B_BAD_VALUE;
}
}
for (uint32 i = length; i < LABEL_LENGTH; i++)
label[i] = ' ';
PRINT("label_to_fat: converted to [%11.11s].\n", label);
return B_OK;
}
@param name A character array of length 11 or a C string of size 12.
@post Name is null-terminated after the last non-space character, and converted to lower case.
*/
void
label_from_fat(char* name)
{
int i;
for (i = 10; i > 0; i--) {
if (name[i] != ' ')
break;
}
name[i + 1] = 0;
for (; i >= 0; i--) {
if (name[i] >= 'A' && name[i] <= 'Z')
name[i] += 'a' - 'A';
}
}
@param fd The device file, specified independently of volume because in
dosfs_identify_partition, volume->pm_dev will not be initialized.
@param label The pre-allocated (LABEL_CSTRING bytes) string into which the label is copied.
@pre The following members of volume are initialized: pm_rootdirsize, pm_BlkPerSec,
pm_BytesPerSec, pm_bpcluster, pm_rootdirblk, pm_bnshift. For a FAT32 volume,
pm_firstcluster must also be initialized.
@post If volume is an old DOS 3.3 era FAT volume, which will not have a label, then label is
set to an empty string and the function returns B_OK. For other volumes, the root
directory label is returned in label, if found; if not, the BPB label is returned.
*/
status_t
read_label(const msdosfsmount* volume, int fd, const uint8* buffer, char* label)
{
*label = '\0';
bool fat32 = FAT32(volume);
uint8 check = fat32 == true ? 0x42 : 0x26;
uint8 offset = fat32 == true ? 0x47 : 0x2b;
if (buffer[check] == EXBOOTSIG && memcmp(buffer + offset, " ", LABEL_LENGTH) != 0) {
memcpy(label, buffer + offset, LABEL_LENGTH);
label_from_fat(label);
} else {
return B_OK;
}
if (volume->pm_mountp != NULL)
volume->pm_mountp->mnt_volentry = -1;
int32 rootDirBytes;
if (fat32 == true)
rootDirBytes = volume->pm_bpcluster;
else
rootDirBytes = (volume->pm_rootdirsize / volume->pm_BlkPerSec) * volume->pm_BytesPerSec;
uint8* rootDirBuffer = static_cast<uint8*>(calloc(rootDirBytes, sizeof(char)));
daddr_t rootDirBlock = volume->pm_rootdirblk;
if (fat32 == true)
rootDirBlock = cntobn(volume, rootDirBlock);
off_t rootDirPos = de_bn2off(volume, rootDirBlock);
int32 bytesRead = read_pos(fd, rootDirPos, rootDirBuffer, rootDirBytes);
if (bytesRead != rootDirBytes) {
free(rootDirBuffer);
RETURN_ERROR(B_IO_ERROR);
}
uint32 direntrySize = sizeof(struct direntry);
uint32 rootDirEntries = rootDirBytes / direntrySize;
struct direntry* direntry = NULL;
for (uint32 i = 0;
i < rootDirEntries && (direntry == NULL || direntry->deName[0] != SLOT_EMPTY); ++i) {
direntry = reinterpret_cast<struct direntry*>(rootDirBuffer + direntrySize * i);
if (direntry->deName[0] != SLOT_DELETED && direntry->deAttributes != ATTR_WIN95
&& (direntry->deAttributes & ATTR_VOLUME) != 0) {
memcpy(label, direntry->deName, 11);
label_from_fat(label);
PRINT("found volume label in root directory: %s at i = %" B_PRIu32 "\n", label, i);
if (volume->pm_mountp != NULL)
volume->pm_mountp->mnt_volentry = i;
break;
}
}
free(rootDirBuffer);
return B_OK;
}
FAT12 or FAT16, if it is formatted in the old DOS 3.3 format.
*/
status_t
check_bootsector(const uint8* bootsector, FatType& _type, bool& _dos33)
{
if (bootsector[0x1fe] != BOOTSIG0 || bootsector[0x1ff] != BOOTSIG1)
return B_BAD_VALUE;
if (!memcmp((uint8*) bootsector + 3, "NTFS ", 8)
|| !memcmp((uint8*) bootsector + 3, "HPFS ", 8)) {
return B_BAD_VALUE;
}
uint32 bytesPerSector = read16(bootsector, 0Xb);
if (bytesPerSector != 512 && bytesPerSector != 1024 && bytesPerSector != 2048
&& bytesPerSector != 4096) {
return B_BAD_VALUE;
}
uint32 sectorsPerCluster = bootsector[0xd];
if (sectorsPerCluster == 0)
return B_BAD_VALUE;
uint32 rootDirEntries = read16(bootsector, 0x11);
uint32 rootDirSectors = ((rootDirEntries * 32) + (bytesPerSector - 1)) / bytesPerSector;
uint32 sectorsPerFat = read16(bootsector, 0x16);
if (sectorsPerFat == 0)
sectorsPerFat = read32(bootsector, 0x24);
uint32 totalSectors = read16(bootsector, 0x13);
if (totalSectors == 0)
totalSectors = read32(bootsector, 0x20);
uint32 reservedSectors = read16(bootsector, 0xe);
uint32 fatCount = bootsector[0x10];
uint32 dataSectors
= totalSectors - (reservedSectors + (fatCount * sectorsPerFat) + rootDirSectors);
uint32 clusterCount = dataSectors / sectorsPerCluster;
if (clusterCount < 4085)
_type = fat12;
else if (clusterCount < 65525)
_type = fat16;
else
_type = fat32;
if (_type != fat32 && bootsector[0x26] != EXBOOTSIG)
_dos33 = true;
else
_dos33 = false;
return B_OK;
}
@pre Volume is already allocated and pm_fatmask has been set; if it is a temp object created
for use in dosfs_identify_partition, that will be indicated by a NULL volume->pm_mount.
@post Those members of volume that can be read directly from the BPB are initialized.
pm_HugeSectors will be initialized, regardless of which field the BPB stores the sector count
in (in contrast, pm_Sectors will not always contain an accurate count).
*/
status_t
parse_bpb(msdosfsmount* volume, const bootsector* bootsector, bool dos33)
{
status_t status = B_OK;
const universal_byte_bpb* bpb
= reinterpret_cast<const universal_byte_bpb*>(bootsector->bs33.bsBPB);
volume->pm_BytesPerSec = getushort(bpb->bpbBytesPerSec);
volume->pm_bpb.bpbSecPerClust = bpb->bpbSecPerClust;
volume->pm_ResSectors = getushort(bpb->bpbResSectors);
volume->pm_FATs = bpb->bpbFATs;
volume->pm_RootDirEnts = getushort(bpb->bpbRootDirEnts);
volume->pm_Sectors = getushort(bpb->bpbSectors);
volume->pm_Media = bpb->bpbMedia;
volume->pm_FATsecs = volume->pm_bpb.bpbFATsecs = getushort(bpb->bpbFATsecs);
volume->pm_SecPerTrack = getushort(bpb->bpbSecPerTrack);
volume->pm_Heads = getushort(bpb->bpbHeads);
if (volume->pm_BytesPerSec != 0x200 && volume->pm_BytesPerSec != 0x400
&& volume->pm_BytesPerSec != 0x800 && volume->pm_BytesPerSec != 0x1000) {
INFORM("invalid bytes per sector (%d)\n", volume->pm_BytesPerSec);
status = B_BAD_VALUE;
} else if (volume->pm_BytesPerSec < DEV_BSIZE) {
INFORM("invalid bytes per sector (%d)\n", volume->pm_BytesPerSec);
status = B_BAD_VALUE;
} else if (volume->pm_bpb.bpbSecPerClust != 1 && volume->pm_bpb.bpbSecPerClust != 2
&& volume->pm_bpb.bpbSecPerClust != 4 && volume->pm_bpb.bpbSecPerClust != 8
&& volume->pm_bpb.bpbSecPerClust != 16 && volume->pm_bpb.bpbSecPerClust != 32
&& volume->pm_bpb.bpbSecPerClust != 64 && volume->pm_bpb.bpbSecPerClust != 128) {
INFORM("invalid sectors per cluster (%" B_PRIu8 ")\n", volume->pm_bpb.bpbSecPerClust);
status = B_BAD_VALUE;
} else if (volume->pm_BytesPerSec * volume->pm_bpb.bpbSecPerClust > 0x10000) {
INFORM("invalid bytes per cluster (%" B_PRIu16 ")\n",
volume->pm_BytesPerSec * volume->pm_bpb.bpbSecPerClust);
status = B_BAD_VALUE;
} else if (volume->pm_FATs == 0 || volume->pm_FATs > 8) {
INFORM("unreasonable fat count (%" B_PRIu8 ")\n", volume->pm_FATs);
status = B_BAD_VALUE;
} else if (volume->pm_Media != 0xf0 && volume->pm_Media < 0xf8) {
INFORM("invalid media descriptor byte\n");
status = B_BAD_VALUE;
}
if (status != B_OK)
return status;
if (dos33 == true && (FAT12(volume) != 0 || FAT16(volume) != 0)) {
const struct byte_bpb33* b33 = reinterpret_cast<const byte_bpb33*>(bootsector->bs33.bsBPB);
if (volume->pm_Sectors == 0) {
INFORM("sector count missing\n");
status = B_BAD_VALUE;
} else {
volume->pm_HugeSectors = volume->pm_Sectors;
volume->pm_HiddenSects = getushort(b33->bpbHiddenSecs);
volume->pm_flags |= MSDOSFS_FATMIRROR;
}
} else if (FAT12(volume) != 0 || FAT16(volume) != 0) {
const struct byte_bpb50* b50 = reinterpret_cast<const byte_bpb50*>(bootsector->bs50.bsBPB);
if (volume->pm_Sectors == 0)
volume->pm_HugeSectors = getulong(b50->bpbHugeSectors);
else
volume->pm_HugeSectors = volume->pm_Sectors;
volume->pm_HiddenSects = getulong(b50->bpbHiddenSecs);
volume->pm_flags |= MSDOSFS_FATMIRROR;
if (FAT16(volume) && volume->pm_mountp != NULL)
fix_zip(b50, volume);
} else if (FAT32(volume) != 0) {
const struct byte_bpb710* b710
= reinterpret_cast<const byte_bpb710*>(bootsector->bs710.bsBPB);
volume->pm_HiddenSects = getulong(b710->bpbHiddenSecs);
volume->pm_HugeSectors = getulong(b710->bpbHugeSectors);
volume->pm_FATsecs = getulong(b710->bpbBigFATsecs);
if ((getushort(b710->bpbExtFlags) & FATMIRROR) != 0)
volume->pm_curfat = getushort(b710->bpbExtFlags) & FATNUM;
else
volume->pm_flags |= MSDOSFS_FATMIRROR;
volume->pm_rootdirblk = getulong(b710->bpbRootClust);
volume->pm_fsinfo = getushort(b710->bpbFSInfo);
} else {
status = B_UNSUPPORTED;
}
if (status != B_OK)
return status;
if (FAT12(volume) || FAT16(volume)) {
if ((volume->pm_RootDirEnts * 32) % volume->pm_BytesPerSec != 0) {
INFORM("invalid number of root dir entries\n");
status = B_BAD_VALUE;
}
} else if (FAT32(volume)) {
const struct byte_bpb710* b710
= reinterpret_cast<const byte_bpb710*>(bootsector->bs710.bsBPB);
if (getushort(b710->bpbSectors) != 0 || getushort(b710->bpbFSVers) != 0
|| getushort(b710->bpbRootDirEnts) != 0 || getushort(b710->bpbFATsecs) != 0) {
INFORM("bad FAT32 filesystem\n");
status = B_BAD_VALUE;
}
}
if ((off_t) volume->pm_HugeSectors * volume->pm_BytesPerSec < volume->pm_HugeSectors) {
INFORM("sectors overflow\n");
status = B_BAD_VALUE;
}
if (volume->pm_mountp != NULL) {
if (static_cast<off_t>(volume->pm_HugeSectors) * volume->pm_BytesPerSec
> volume->pm_dev->si_mediasize) {
INFORM("sectors past end of vol: %u sectors on a %" B_PRIdOFF "-byte volume\n",
volume->pm_HugeSectors, volume->pm_dev->si_mediasize);
status = B_BAD_VALUE;
}
}
return status;
}
They say that they have 196576 sectors but they really only have 196192.
This check is a work-around for their brain-deadness.
@pre volume->pm_HugeSectors and volume->pm_dev have been initialized.
*/
void
fix_zip(const byte_bpb50* bpb, msdosfsmount* volume)
{
device_geometry* geo = volume->pm_dev->si_geometry;
if (geo != NULL) {
unsigned char bogusZipData[] = {0x00, 0x02, 0x04, 0x01, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00,
0xf8, 0xc0, 0x00, 0x20, 0x00, 0x40, 0x00, 0x20, 0x00, 0x00};
if (memcmp(bpb, bogusZipData, sizeof(bogusZipData)) == 0 && volume->pm_HugeSectors == 196576
&& (static_cast<off_t>(geo->sectors_per_track) * static_cast<off_t>(geo->cylinder_count)
* static_cast<off_t>(geo->head_count))
== 196192) {
volume->pm_HugeSectors = 196192;
}
}
return;
}
@pre devNode and the block cache are initialized. The volume is still in the process
of being mounted, so we have exclusive access to the sector (there is no need for a read lock).
*/
status_t
read_fsinfo(msdosfsmount* volume, const vnode* devNode)
{
status_t status = B_OK;
if (FAT32(volume) != 0) {
const uint8* buffer;
const struct fsinfo* fsInfo;
off_t cachedBlock = BLOCK_TO_SECTOR(volume, volume->pm_fsinfo);
status = block_cache_get_etc(volume->pm_mountp->mnt_cache, cachedBlock,
reinterpret_cast<const void**>(&buffer));
if (status != B_OK)
RETURN_ERROR(status);
fsInfo = reinterpret_cast<const fsinfo*>(buffer);
if (memcmp(fsInfo->fsisig1, "RRaA", 4) == 0 && memcmp(fsInfo->fsisig2, "rrAa", 4) == 0
&& memcmp(fsInfo->fsisig3, "\0\0\125\252", 4) == 0) {
volume->pm_nxtfree = getulong(fsInfo->fsinxtfree);
if (volume->pm_nxtfree > volume->pm_maxcluster)
volume->pm_nxtfree = CLUST_FIRST;
} else {
INFORM("fsinfo block has invalid magic number\n");
status = B_BAD_VALUE;
volume->pm_fsinfo = 0;
}
block_cache_put(volume->pm_mountp->mnt_cache, cachedBlock);
}
* Finish initializing pmp->pm_nxtfree (just in case the first few
* sectors aren't properly reserved in the FAT). This completes
* the fixup for fp->fsinxtfree, and fixes up the zero-initialized
* value if there is no fsinfo. We will use pmp->pm_nxtfree
* internally even if there is no fsinfo.
*/
if (volume->pm_nxtfree < CLUST_FIRST)
volume->pm_nxtfree = CLUST_FIRST;
return status;
}
@pre the block cache is initialized.
*/
status_t
write_fsinfo(msdosfsmount* volume)
{
if (volume->pm_fsinfo == 0 || FAT32(volume) == false || (volume->pm_flags & MSDOSFS_FSIMOD) == 0
|| (volume->pm_flags & MSDOSFSMNT_RONLY) != 0) {
return B_OK;
}
off_t cachedBlock = BLOCK_TO_SECTOR(volume, volume->pm_fsinfo);
void* buffer = block_cache_get_writable(volume->pm_mountp->mnt_cache, cachedBlock, -1);
if (buffer == NULL)
RETURN_ERROR(B_ERROR);
struct fsinfo* fsInfo = reinterpret_cast<struct fsinfo*>(buffer);
if (memcmp(fsInfo->fsisig1, "RRaA", 4) != 0 || memcmp(fsInfo->fsisig2, "rrAa", 4) != 0
|| memcmp(fsInfo->fsisig3, "\0\0\125\252", 4) != 0) {
block_cache_set_dirty(volume->pm_mountp->mnt_cache, cachedBlock, false, -1);
block_cache_put(volume->pm_mountp->mnt_cache, cachedBlock);
RETURN_ERROR(B_ERROR);
}
putulong(fsInfo->fsinfree, volume->pm_freeclustercount);
putulong(fsInfo->fsinxtfree, volume->pm_nxtfree);
volume->pm_flags &= ~MSDOSFS_FSIMOD;
block_cache_put(volume->pm_mountp->mnt_cache, cachedBlock);
return B_OK;
}
that is done by fillinusemap().
*/
status_t
check_fat(const msdosfsmount* volume)
{
uint32 bytesPerSec = volume->pm_BytesPerSec;
uint32 fatSectors = volume->pm_FATsecs / volume->pm_BlkPerSec;
uint8 fatBuffer[bytesPerSec];
uint8 mirrorBuffer[bytesPerSec];
uint32 checkSectors = fatSectors > 4096 ? 1 : fatSectors;
PRINT("check_fat checking %" B_PRIu32 " sectors\n", checkSectors);
for (uint32 i = 0; i < checkSectors; ++i) {
uint32 resSectors = volume->pm_ResSectors;
off_t position = bytesPerSec * (resSectors + volume->pm_curfat * fatSectors + i);
ssize_t bytes_read
= read_pos(volume->pm_dev->si_fd, position, reinterpret_cast<void*>(fatBuffer), bytesPerSec);
if (bytes_read != static_cast<ssize_t>(bytesPerSec))
RETURN_ERROR(B_IO_ERROR);
for (uint32 j = 0; j < volume->pm_FATs; ++j) {
if (j == volume->pm_curfat)
continue;
position = bytesPerSec * (resSectors + fatSectors * j + i);
bytes_read = read_pos(volume->pm_dev->si_fd, position,
reinterpret_cast<void*>(mirrorBuffer), bytesPerSec);
if (bytes_read != static_cast<ssize_t>(bytesPerSec))
RETURN_ERROR(B_IO_ERROR);
if (i == 0 && mirrorBuffer[0] != volume->pm_Media) {
INFORM("dosfs error: media descriptor mismatch in fat # "
"%" B_PRIu32 " (%" B_PRIu8 " != %" B_PRIu8 ")\n",
j, mirrorBuffer[0], volume->pm_Media);
return B_BAD_VALUE;
}
if (memcmp(fatBuffer, mirrorBuffer, bytesPerSec)) {
INFORM("FAT %" B_PRIu32 " doesn't match active FAT (%u) on %s.\n"
"Install dosfstools and use fsck.fat to inspect %s.\n",
j, volume->pm_curfat, volume->pm_dev->si_device, volume->pm_dev->si_device);
}
}
}
return B_OK;
}
E.g. for n = 1, return the cluster that is next in the chain after 'cluster'
If 'cluster' is the last in the chain, returns the last-cluster value.
*/
uint32
get_nth_fat_entry(msdosfsmount* fatVolume, uint32 cluster, uint32 n)
{
int status = 0;
ReadLocker locker(fatVolume->pm_fatlock.haikuRW);
u_long resultCluster = static_cast<u_long>(cluster);
while (n--) {
status = fatentry(FAT_GET, fatVolume, resultCluster, &resultCluster, 0);
if (status != 0) {
REPORT_ERROR(B_FROM_POSIX_ERROR(status));
break;
}
ASSERT(IS_DATA_CLUSTER(resultCluster));
}
return static_cast<uint32>(resultCluster);
}
status_t
init_csi(msdosfsmount* fatVolume, uint32 cluster, uint32 sector, struct csi* csi)
{
int ret;
if ((ret = validate_cs(fatVolume, cluster, sector)) != B_OK)
return ret;
csi->fatVolume = fatVolume;
csi->cluster = cluster;
csi->sector = sector;
return B_OK;
}
status_t
validate_cs(msdosfsmount* fatVolume, uint32 cluster, uint32 sector)
{
if (cluster == MSDOSFSROOT) {
if (sector >= fatVolume->pm_rootdirsize / fatVolume->pm_BlkPerSec)
return B_ERROR;
return B_OK;
}
if (sector >= SECTORS_PER_CLUSTER(fatVolume)) {
INFORM("validate_cs: invalid sector (%" B_PRIu32 ")\n", sector);
return B_ERROR;
}
if (!IS_DATA_CLUSTER(cluster)) {
INFORM("validate_cs: cluster %" B_PRIu32 " compared to max %lu\n", cluster,
fatVolume->pm_maxcluster);
return B_ERROR;
}
return B_OK;
}
*/
off_t
fs_sector(struct csi* csi)
{
ASSERT(validate_cs(csi->fatVolume, csi->cluster, csi->sector) == 0);
if (csi->cluster == MSDOSFSROOT) {
return static_cast<off_t>(csi->fatVolume->pm_rootdirblk) / csi->fatVolume->pm_BlkPerSec
+ csi->sector;
}
off_t clusterStart
= cntobn(csi->fatVolume, static_cast<off_t>(csi->cluster)) / csi->fatVolume->pm_BlkPerSec;
return clusterStart + csi->sector;
}
If it's not possible to advance this many sectors the function returns B_ERROR.
*/
status_t
iter_csi(struct csi* csi, int sectors)
{
if (csi->sector == 0xffff)
return B_ERROR;
if (sectors < 0)
return B_BAD_VALUE;
if (sectors == 0)
return B_OK;
if (csi->cluster == MSDOSFSROOT) {
csi->sector += sectors;
if (csi->sector < csi->fatVolume->pm_rootdirsize / csi->fatVolume->pm_BlkPerSec)
return B_OK;
} else {
u_long secPerClust = SECTORS_PER_CLUSTER(csi->fatVolume);
csi->sector += sectors;
if (csi->sector < secPerClust)
return B_OK;
csi->cluster = get_nth_fat_entry(csi->fatVolume, csi->cluster, csi->sector / secPerClust);
if (MSDOSFSEOF(csi->fatVolume, csi->cluster)) {
csi->sector = 0xffff;
return B_ERROR;
}
if (vIS_DATA_CLUSTER(csi->fatVolume, csi->cluster)) {
csi->sector %= SECTORS_PER_CLUSTER(csi->fatVolume);
return B_OK;
}
}
csi->sector = 0xffff;
return B_ERROR;
}
NodePutter::NodePutter()
:
fNode(NULL)
{
}
NodePutter::NodePutter(vnode* node)
:
fNode(node)
{
}
NodePutter::~NodePutter()
{
Put();
}
void
NodePutter::SetTo(vnode* node)
{
fNode = node;
}
void
NodePutter::Put()
{
if (fNode != NULL) {
fs_volume* volume = fNode->v_mount->mnt_fsvolume;
denode* fatNode = reinterpret_cast<denode*>(fNode->v_data);
ino_t ino = static_cast<ino_t>(fatNode->de_inode);
status_t status = put_vnode(volume, ino);
if (status != B_OK)
REPORT_ERROR(status);
fNode = NULL;
}
}
The final number will be different if the directory entry slot of a deleted or renamed file
is re-used. Also add the node to the vcache if it isn't already added. Unlike the original
Haiku FAT driver, each node is added to the vcache (not just those with artificial ID #s).
*/
status_t
assign_inode(mount* volume, ino_t* inode)
{
ino_t location = *inode;
status_t result = B_OK;
if (*inode == root_inode(reinterpret_cast<msdosfsmount*>(volume->mnt_data)))
return B_OK;
ino_t finalInode = 0;
result = vcache_loc_to_vnid(volume, location, &finalInode);
if (result == ENOENT) {
if (find_vnid_in_vcache(volume, location) == B_OK) {
finalInode = generate_unique_vnid(volume);
if ((result = add_to_vcache(volume, finalInode, location)) < 0)
RETURN_ERROR(result);
} else {
finalInode = location;
if ((result = add_to_vcache(volume, finalInode, location)) < 0)
RETURN_ERROR(result);
}
} else if (result != B_OK) {
RETURN_ERROR(result);
}
PRINT("assign_inode in: %" B_PRIdINO ", out: %" B_PRIdINO "\n", *inode, finalInode);
*inode = finalInode;
return B_OK;
}
status_t
assign_inode_and_get(mount* bsdVolume, daddr_t cluster, u_long offset, vnode** bsdNode)
{
msdosfsmount* fatVolume = reinterpret_cast<msdosfsmount*>(bsdVolume->mnt_data);
ino_t ino = DETOI(fatVolume, cluster, offset);
status_t status = assign_inode(bsdVolume, &ino);
if (status != B_OK)
RETURN_ERROR(B_ENTRY_NOT_FOUND);
fs_volume* fsVolume = bsdVolume->mnt_fsvolume;
status = get_vnode(fsVolume, ino, reinterpret_cast<void**>(bsdNode));
if (status != B_OK)
RETURN_ERROR(B_ENTRY_NOT_FOUND);
return B_OK;
}
@param vnid A final inode number (possibly location-based, possibly artificial).
@post dirclust and diroffset specify the location of the direntry associated with vnid. If the
location is in the (FAT12/16) root directory, diroffset is relative to the start of the root
directory; otherwise it is cluster-relative.
*/
status_t
get_location(mount* bsdVolume, ino_t vnid, u_long* dirclust, u_long* diroffset)
{
msdosfsmount* fatVolume = reinterpret_cast<msdosfsmount*>(bsdVolume->mnt_data);
ino_t location = -1;
status_t status = vcache_vnid_to_loc(bsdVolume, vnid, &location);
if (status != B_OK)
return status;
if (dirclust != NULL && diroffset != NULL) {
if (static_cast<unsigned long>(location) < fatVolume->pm_RootDirEnts) {
*dirclust = MSDOSFSROOT;
*diroffset = location << 5;
} else {
location -= fatVolume->pm_RootDirEnts;
location <<= 5;
*dirclust = (location / fatVolume->pm_bpcluster) + 2;
*diroffset = location % fatVolume->pm_bpcluster;
}
}
return B_OK;
}
bool
node_exists(mount* bsdVolume, uint64_t inode)
{
bool constructed = false;
vcache_get_constructed(bsdVolume, inode, &constructed);
return constructed;
}
*/
void
timestamp_local(timespec* tsp)
{
bigtime_t usecs = real_time_clock_usecs();
tsp->tv_sec = (usecs / 1000000LL);
tsp->tv_nsec = (usecs - tsp->tv_sec * 1000000LL) * 1000LL;
return;
}
void
local_to_GMT(const timespec* tspLocal, timespec* tspGMT)
{
*tspGMT = *tspLocal;
int32 offset = 0;
#ifdef _KERNEL_MODE
offset = static_cast<int32>(get_timezone_offset());
#elif defined USER
time_t localTime;
time(&localTime);
struct tm localTm;
localtime_r(&localTime, &localTm);
offset = localTm.tm_gmtoff;
#endif
tspGMT->tv_sec -= offset;
return;
}
@param deviceNode The node that represents the mounted device, whose v_bufobj member is to be
updated.
@param size The required size of buf::b_data.
@pre The msdosfsmount has been initialized and v_bufobj.bo_lock is write locked.
@post A new buf is inserted at the head of the bufobj SLIST that corresponds to 'size.' C-style
allocation so the buf can be freed in C code.
*/
status_t
slist_insert_buf(vnode* deviceNode, size_t size)
{
buf_list* list = NULL;
uint32* count = NULL;
msdosfsmount* fatVolume
= reinterpret_cast<msdosfsmount*>(deviceNode->v_rdev->si_mountpt->mnt_data);
if (size == 0) {
list = &deviceNode->v_bufobj.bo_emptybufs;
count = &deviceNode->v_bufobj.bo_empties;
} else if (size == fatVolume->pm_fatblocksize) {
list = &deviceNode->v_bufobj.bo_fatbufs;
count = &deviceNode->v_bufobj.bo_fatblocks;
} else if (size == fatVolume->pm_bpcluster) {
list = &deviceNode->v_bufobj.bo_clusterbufs;
count = &deviceNode->v_bufobj.bo_clusters;
} else {
return B_UNSUPPORTED;
}
buf* newBuf = reinterpret_cast<buf*>(calloc(1, sizeof(buf)));
if (newBuf == NULL)
return B_NO_MEMORY;
if (size != 0) {
newBuf->b_data = reinterpret_cast<caddr_t>(calloc(size, sizeof(char)));
if (newBuf->b_data == NULL) {
free(newBuf);
return B_NO_MEMORY;
}
}
newBuf->b_bufsize = size;
SLIST_INSERT_HEAD(list, newBuf, link);
(*count)++;
return B_OK;
}
status_t
fill_gap_with_zeros(vnode* bsdNode, off_t pos, off_t newSize)
{
while (pos < newSize) {
size_t size;
if (newSize > pos + 1024 * 1024 * 1024)
size = 1024 * 1024 * 1024;
else
size = newSize - pos;
status_t status = file_cache_write(bsdNode->v_cache, NULL, pos, NULL, &size);
if (status < B_OK)
return status;
pos += size;
}
return B_OK;
}
*/
status_t
sync_clusters(vnode* bsdNode)
{
mount* bsdVolume = bsdNode->v_mount;
denode* fatNode = reinterpret_cast<denode*>(bsdNode->v_data);
msdosfsmount* fatVolume = fatNode->de_pmp;
status_t status = B_OK;
ASSERT(bsdNode->v_type == VDIR);
u_long cluster = fatNode->de_dirclust;
if (cluster == MSDOSFSROOT) {
off_t cachedBlock = BLOCK_TO_SECTOR(fatVolume, fatVolume->pm_rootdirblk);
size_t numBlocks =
HOWMANY(fatVolume->pm_rootdirsize * DEV_BSIZE, fatVolume->pm_BytesPerSec);
status = block_cache_sync_etc(bsdVolume->mnt_cache, cachedBlock, numBlocks);
} else {
status_t fatStatus = B_OK;
while ((IS_DATA_CLUSTER(cluster)) && status == B_OK && fatStatus == B_OK) {
off_t cachedBlock = BLOCK_TO_SECTOR(fatVolume, cntobn(fatVolume, cluster));
status = block_cache_sync_etc(bsdVolume->mnt_cache, cachedBlock,
SECTORS_PER_CLUSTER(fatVolume));
fatStatus = B_FROM_POSIX_ERROR(fatentry(FAT_GET, fatVolume, cluster, &cluster, 0));
}
if (fatStatus != B_OK)
REPORT_ERROR(fatStatus);
}
RETURN_ERROR(status);
}
directory in FAT12/16; those blocks will be discarded when the volume is unmounted.
*/
status_t
discard_clusters(vnode* bsdNode, off_t newLength)
{
mount* bsdVolume = bsdNode->v_mount;
denode* fatNode = reinterpret_cast<denode*>(bsdNode->v_data);
msdosfsmount* fatVolume = fatNode->de_pmp;
status_t status = B_OK;
ASSERT(bsdNode->v_type == VDIR);
u_long cluster = fatNode->de_dirclust;
for (uint32 skip = HOWMANY(newLength, fatVolume->pm_bpcluster); skip > 0 && status == B_OK;
skip--) {
status = B_FROM_POSIX_ERROR(fatentry(FAT_GET, fatVolume, cluster, &cluster, 0));
}
while ((IS_DATA_CLUSTER(cluster)) && status == B_OK) {
off_t cachedBlock = BLOCK_TO_SECTOR(fatVolume, cntobn(fatVolume, cluster));
block_cache_discard(bsdVolume->mnt_cache, cachedBlock, SECTORS_PER_CLUSTER(fatVolume));
status = B_FROM_POSIX_ERROR(fatentry(FAT_GET, fatVolume, cluster, &cluster, 0));
}
RETURN_ERROR(status);
}
Limitation: ignores permissions granted by the user's group
*/
status_t
check_access_permissions_internal(int accessMode, mode_t mode, gid_t nodeGroupID, uid_t nodeUserID)
{
int userPermissions = (mode & S_IRWXU) >> 6;
int groupPermissions = (mode & S_IRWXG) >> 3;
int otherPermissions = mode & S_IRWXO;
int permissions = 0;
uid_t uid = geteuid();
if (uid == 0) {
permissions = userPermissions | groupPermissions | otherPermissions | S_IROTH | S_IWOTH;
if (S_ISDIR(mode))
permissions |= S_IXOTH;
} else if (uid == nodeUserID) {
permissions = userPermissions;
} else {
permissions = otherPermissions;
}
return (accessMode & ~permissions) == 0 ? B_OK : B_PERMISSION_DENIED;
}
*/
void
mode_bits(const vnode* bsdNode, mode_t* mode)
{
denode* fatNode = reinterpret_cast<denode*>(bsdNode->v_data);
msdosfsmount* fatVolume = fatNode->de_pmp;
*mode = S_IRUSR | S_IRGRP | S_IROTH;
if ((fatNode->de_Attributes & ATTR_READONLY) == 0)
*mode |= S_IWUSR;
if (bsdNode->v_type == VDIR
|| (bsdNode->v_type == VREG && bsdNode->v_mime != NULL
&& strcmp(bsdNode->v_mime, "application/octet-stream") == 0)) {
*mode |= S_IXUSR | S_IXGRP | S_IXOTH;
}
*mode &= (bsdNode->v_type == VDIR) ? fatVolume->pm_dirmask : fatVolume->pm_mask;
return;
}
@param update True if this is an update to a pre-existing mime setting.
*/
status_t
set_mime_type(vnode* bsdNode, bool update)
{
#ifndef FS_SHELL
mount* bsdVolume = reinterpret_cast<mount*>(bsdNode->v_mount);
denode* fatNode = reinterpret_cast<denode*>(bsdNode->v_data);
msdosfsmount* fatVolume = reinterpret_cast<msdosfsmount*>(fatNode->de_pmp);
if (bsdNode->v_type == VREG) {
char unixShortname[SHORTNAME_CSTRING + 1];
dos2unixfn(fatNode->de_Name, reinterpret_cast<u_char*>(unixShortname), 0, fatVolume);
set_mime(&bsdNode->v_mime, unixShortname);
notify_attribute_changed(bsdVolume->mnt_fsvolume->id, bsdNode->v_parent, fatNode->de_inode,
"BEOS:TYPE", update ? B_ATTR_CHANGED : B_ATTR_CREATED);
}
#endif
return B_OK;
}
The FAT filesystem assigns both a short name and a long name to each file/directory.
The short names are encoded in an 8-bit or DBCS OEM code page, aka DOS code page.
Short names must be unique within a directory.
If the user assigns a name containing a character not in the OEM code page, then
the short name will substitute an underscore character.
Users only see the long name, but collisions between short entry names are more likely if
the OEM code page is not suitable for the user's language.
FAT FS will attempt to resolve collisions by adding a generation number to the new
short filename (also invisible to the user).
Many code pages are supported by libiconv, which we use in the the userlandfs module.
libiconv is not available in the FS shell or the kernel; in those cases the driver defaults to
an internal copy of code page 850.
*/
status_t
iconv_init(msdosfsmount* fatVolume, const char* oemPreference)
{
fatVolume->pm_u2w = NULL;
fatVolume->pm_w2u = NULL;
fatVolume->pm_u2d = NULL;
fatVolume->pm_d2u = NULL;
#ifndef USER
return B_OK;
#else
if ((fatVolume->pm_flags & MSDOSFSMNT_KICONV) == 0 || strcmp(oemPreference, "") == 0)
return B_OK;
msdosfs_iconv = new(std::nothrow) iconv_functions;
if (msdosfs_iconv == NULL)
RETURN_ERROR(B_NO_MEMORY);
ObjectDeleter<iconv_functions> deleter(msdosfs_iconv);
msdosfs_iconv->open = fat_iconv_open;
msdosfs_iconv->close = fat_iconv_close;
msdosfs_iconv->conv = iconv_conv;
msdosfs_iconv->convchr = iconv_convchr;
msdosfs_iconv->convchr_case = iconv_convchr_case;
PRINT("setting code page to %s\n", oemPreference);
const char* haiku = "UTF-8";
const char* windows = "UCS-2";
const char* dos = oemPreference;
status_t status = B_OK;
status = B_FROM_POSIX_ERROR(msdosfs_iconv->open(windows, haiku, &fatVolume->pm_u2w));
if (status != B_OK)
RETURN_ERROR(errno);
status = B_FROM_POSIX_ERROR(msdosfs_iconv->open(haiku, windows, &fatVolume->pm_w2u));
if (status != B_OK)
RETURN_ERROR(errno);
status = B_FROM_POSIX_ERROR(msdosfs_iconv->open(dos, haiku, &fatVolume->pm_u2d));
if (status != B_OK)
RETURN_ERROR(errno);
status = B_FROM_POSIX_ERROR(msdosfs_iconv->open(haiku, dos, &fatVolume->pm_d2u));
if (status != B_OK)
RETURN_ERROR(errno);
deleter.Detach();
return B_OK;
#endif
}