/* * Write data to a file or directory. */ static int msdosfs_wfile(const char *path, struct denode *dep, fsnode *node) { int error, fd; size_t osize = dep->de_FileSize; struct stat *st = &node->inode->st; size_t nsize, offs; struct msdosfsmount *pmp = dep->de_pmp; struct buf *bp; char *dat; u_long cn = 0; error = 0; /* XXX: gcc/vax */ DPRINTF(("%s(diroff %lu, dirclust %lu, startcluster %lu)\n", __func__, dep->de_diroffset, dep->de_dirclust, dep->de_StartCluster)); if (st->st_size == 0) return 0; /* Don't bother to try to write files larger than the fs limit */ if (st->st_size > MSDOSFS_FILESIZE_MAX) { errno = EFBIG; return -1; } nsize = st->st_size; DPRINTF(("%s(nsize=%zu, osize=%zu)\n", __func__, nsize, osize)); if (nsize > osize) { if ((error = deextend(dep, nsize, NULL)) != 0) { errno = error; return -1; } if ((error = msdosfs_updatede(dep)) != 0) { errno = error; return -1; } } if ((fd = open(path, O_RDONLY)) == -1) err(1, "open %s", path); if ((dat = mmap(0, nsize, PROT_READ, MAP_FILE | MAP_PRIVATE, fd, 0)) == MAP_FAILED) { DPRINTF(("%s: mmap %s %s", __func__, node->name, strerror(errno))); close(fd); goto out; } close(fd); for (offs = 0; offs < nsize;) { int blsize, cpsize; daddr_t bn; u_long on = offs & pmp->pm_crbomask; #ifdef HACK cn = dep->de_StartCluster; if (cn == MSDOSFSROOT) { DPRINTF(("%s: bad lbn %lu", __func__, cn)); goto out; } bn = cntobn(pmp, cn); blsize = pmp->pm_bpcluster; #else if ((error = pcbmap(dep, cn++, &bn, NULL, &blsize)) != 0) { DPRINTF(("%s: pcbmap %lu", __func__, bn)); goto out; } #endif DPRINTF(("%s(cn=%lu, bn=%llu/%llu, blsize=%d)\n", __func__, cn, (unsigned long long)bn, (unsigned long long)de_bn2kb(pmp, bn), blsize)); if ((error = bread(pmp->pm_devvp, de_bn2kb(pmp, bn), blsize, 0, &bp)) != 0) { DPRINTF(("bread %d\n", error)); goto out; } cpsize = MIN((nsize - offs), blsize - on); memcpy((char *)bp->b_data + on, dat + offs, cpsize); bwrite(bp); offs += cpsize; } munmap(dat, nsize); return 0; out: munmap(dat, nsize); return error; }
/* * Truncate the file described by dep to the length specified by length. */ int detrunc(struct denode *dep, u_long length, int flags, struct ucred *cred) { int error; int allerror; u_long eofentry; u_long chaintofree; daddr_t bn; int boff; int isadir = dep->de_Attributes & ATTR_DIRECTORY; struct buf *bp; struct msdosfsmount *pmp = dep->de_pmp; #ifdef MSDOSFS_DEBUG printf("detrunc(): file %s, length %lu, flags %x\n", dep->de_Name, length, flags); #endif /* * Disallow attempts to truncate the root directory since it is of * fixed size. That's just the way dos filesystems are. We use * the VROOT bit in the vnode because checking for the directory * bit and a startcluster of 0 in the denode is not adequate to * recognize the root directory at this point in a file or * directory's life. */ if ((DETOV(dep)->v_vflag & VV_ROOT) && !FAT32(pmp)) { #ifdef MSDOSFS_DEBUG printf("detrunc(): can't truncate root directory, clust %ld, offset %ld\n", dep->de_dirclust, dep->de_diroffset); #endif return (EINVAL); } if (dep->de_FileSize < length) { vnode_pager_setsize(DETOV(dep), length); return deextend(dep, length, cred); } /* * If the desired length is 0 then remember the starting cluster of * the file and set the StartCluster field in the directory entry * to 0. If the desired length is not zero, then get the number of * the last cluster in the shortened file. Then get the number of * the first cluster in the part of the file that is to be freed. * Then set the next cluster pointer in the last cluster of the * file to CLUST_EOFE. */ if (length == 0) { chaintofree = dep->de_StartCluster; dep->de_StartCluster = 0; eofentry = ~0; } else { error = pcbmap(dep, de_clcount(pmp, length) - 1, 0, &eofentry, 0); if (error) { #ifdef MSDOSFS_DEBUG printf("detrunc(): pcbmap fails %d\n", error); #endif return (error); } } fc_purge(dep, de_clcount(pmp, length)); /* * If the new length is not a multiple of the cluster size then we * must zero the tail end of the new last cluster in case it * becomes part of the file again because of a seek. */ if ((boff = length & pmp->pm_crbomask) != 0) { if (isadir) { bn = cntobn(pmp, eofentry); error = bread(pmp->pm_devvp, bn, pmp->pm_bpcluster, NOCRED, &bp); if (error) { brelse(bp); #ifdef MSDOSFS_DEBUG printf("detrunc(): bread fails %d\n", error); #endif return (error); } bzero(bp->b_data + boff, pmp->pm_bpcluster - boff); if (flags & IO_SYNC) bwrite(bp); else bdwrite(bp); } } /* * Write out the updated directory entry. Even if the update fails * we free the trailing clusters. */ dep->de_FileSize = length; if (!isadir) dep->de_flag |= DE_UPDATE | DE_MODIFIED; allerror = vtruncbuf(DETOV(dep), cred, length, pmp->pm_bpcluster); #ifdef MSDOSFS_DEBUG if (allerror) printf("detrunc(): vtruncbuf error %d\n", allerror); #endif error = deupdat(dep, !DOINGASYNC((DETOV(dep)))); if (error != 0 && allerror == 0) allerror = error; #ifdef MSDOSFS_DEBUG printf("detrunc(): allerror %d, eofentry %lu\n", allerror, eofentry); #endif /* * If we need to break the cluster chain for the file then do it * now. */ if (eofentry != ~0) { error = fatentry(FAT_GET_AND_SET, pmp, eofentry, &chaintofree, CLUST_EOFE); if (error) { #ifdef MSDOSFS_DEBUG printf("detrunc(): fatentry errors %d\n", error); #endif return (error); } fc_setcache(dep, FC_LASTFC, de_cluster(pmp, length - 1), eofentry); } /* * Now free the clusters removed from the file because of the * truncation. */ if (chaintofree != 0 && !MSDOSFSEOF(pmp, chaintofree)) freeclusterchain(pmp, chaintofree); return (allerror); }
/* * Write data to a file or directory. */ int msdosfs_write(void *v) { struct vop_write_args *ap = v; int n; int croffset; int resid; uint32_t osize; int error = 0; uint32_t count, lastcn; daddr64_t bn; struct buf *bp; int ioflag = ap->a_ioflag; struct uio *uio = ap->a_uio; struct proc *p = uio->uio_procp; struct vnode *vp = ap->a_vp; struct vnode *thisvp; struct denode *dep = VTODE(vp); struct msdosfsmount *pmp = dep->de_pmp; struct ucred *cred = ap->a_cred; #ifdef MSDOSFS_DEBUG printf("msdosfs_write(vp %08x, uio %08x, ioflag %08x, cred %08x\n", vp, uio, ioflag, cred); printf("msdosfs_write(): diroff %d, dirclust %d, startcluster %d\n", dep->de_diroffset, dep->de_dirclust, dep->de_StartCluster); #endif switch (vp->v_type) { case VREG: if (ioflag & IO_APPEND) uio->uio_offset = dep->de_FileSize; thisvp = vp; break; case VDIR: return EISDIR; default: panic("msdosfs_write(): bad file type"); } if (uio->uio_offset < 0) return (EINVAL); if (uio->uio_resid == 0) return (0); /* Don't bother to try to write files larger than the f/s limit */ if (uio->uio_offset + uio->uio_resid > MSDOSFS_FILESIZE_MAX) return (EFBIG); /* * If they've exceeded their filesize limit, tell them about it. */ if (p && ((uio->uio_offset + uio->uio_resid) > p->p_rlimit[RLIMIT_FSIZE].rlim_cur)) { psignal(p, SIGXFSZ); return (EFBIG); } /* * If the offset we are starting the write at is beyond the end of * the file, then they've done a seek. Unix filesystems allow * files with holes in them, DOS doesn't so we must fill the hole * with zeroed blocks. */ if (uio->uio_offset > dep->de_FileSize) { if ((error = deextend(dep, uio->uio_offset, cred)) != 0) return (error); } /* * Remember some values in case the write fails. */ resid = uio->uio_resid; osize = dep->de_FileSize; /* * If we write beyond the end of the file, extend it to its ultimate * size ahead of the time to hopefully get a contiguous area. */ if (uio->uio_offset + resid > osize) { count = de_clcount(pmp, uio->uio_offset + resid) - de_clcount(pmp, osize); if ((error = extendfile(dep, count, NULL, NULL, 0)) && (error != ENOSPC || (ioflag & IO_UNIT))) goto errexit; lastcn = dep->de_fc[FC_LASTFC].fc_frcn; } else lastcn = de_clcount(pmp, osize) - 1; do { if (de_cluster(pmp, uio->uio_offset) > lastcn) { error = ENOSPC; break; } bn = de_blk(pmp, uio->uio_offset); if ((uio->uio_offset & pmp->pm_crbomask) == 0 && (de_blk(pmp, uio->uio_offset + uio->uio_resid) > de_blk(pmp, uio->uio_offset) || uio->uio_offset + uio->uio_resid >= dep->de_FileSize)) { /* * If either the whole cluster gets written, * or we write the cluster from its start beyond EOF, * then no need to read data from disk. */ bp = getblk(thisvp, bn, pmp->pm_bpcluster, 0, 0); clrbuf(bp); /* * Do the bmap now, since pcbmap needs buffers * for the fat table. (see msdosfs_strategy) */ if (bp->b_blkno == bp->b_lblkno) { error = pcbmap(dep, de_bn2cn(pmp, bp->b_lblkno), &bp->b_blkno, 0, 0); if (error) bp->b_blkno = -1; } if (bp->b_blkno == -1) { brelse(bp); if (!error) error = EIO; /* XXX */ break; } } else { /* * The block we need to write into exists, so read it in. */ error = bread(thisvp, bn, pmp->pm_bpcluster, NOCRED, &bp); if (error) { brelse(bp); break; } } croffset = uio->uio_offset & pmp->pm_crbomask; n = min(uio->uio_resid, pmp->pm_bpcluster - croffset); if (uio->uio_offset + n > dep->de_FileSize) { dep->de_FileSize = uio->uio_offset + n; uvm_vnp_setsize(vp, dep->de_FileSize); } uvm_vnp_uncache(vp); /* * Should these vnode_pager_* functions be done on dir * files? */ /* * Copy the data from user space into the buf header. */ error = uiomove(bp->b_data + croffset, n, uio); /* * If they want this synchronous then write it and wait for * it. Otherwise, if on a cluster boundary write it * asynchronously so we can move on to the next block * without delay. Otherwise do a delayed write because we * may want to write somemore into the block later. */ if (ioflag & IO_SYNC) (void) bwrite(bp); else if (n + croffset == pmp->pm_bpcluster) bawrite(bp); else bdwrite(bp); dep->de_flag |= DE_UPDATE; } while (error == 0 && uio->uio_resid > 0); /* * If the write failed and they want us to, truncate the file back * to the size it was before the write was attempted. */ errexit: if (error) { if (ioflag & IO_UNIT) { detrunc(dep, osize, ioflag & IO_SYNC, NOCRED, NULL); uio->uio_offset -= resid - uio->uio_resid; uio->uio_resid = resid; } else { detrunc(dep, dep->de_FileSize, ioflag & IO_SYNC, NOCRED, NULL); if (uio->uio_resid != resid) error = 0; } } else if (ioflag & IO_SYNC) error = deupdat(dep, 1); return (error); }