/* * attr.c * * Copyright (C) 1996-1999 Martin von Löwis * Copyright (C) 1996-1997 Régis Duchesne * Copyright (C) 1998 Joseph Malicki * Copyright (C) 1999 Steve Dodd */ #include "ntfstypes.h" #include "struct.h" #include "attr.h" #include #ifdef HAVE_STRING_H #include #endif #include "macros.h" #include "support.h" #include "util.h" #include "super.h" #include "inode.h" /* Look if an attribute already exists in the inode, and if not, create it */ int ntfs_new_attr(ntfs_inode *ino,int type,void *name,int namelen,int *pos, int *found, int do_search ) { int do_insert=0; int i; for(i=0;iattr_count;i++) { int n=min(namelen,ino->attrs[i].namelen); int s=ntfs_uni_strncmp(ino->attrs[i].name,name,n); if( do_search ) { /* * We assume that each attribute can be uniquely * identified by inode * number, attribute type and attribute name. */ if(ino->attrs[i].type==type && ino->attrs[i].namelen==namelen && !s){ *found=1; *pos=i; return 0; } } /* attributes are ordered by type, then by name */ if(ino->attrs[i].type>type || (ino->attrs[i].type==type && s==1)){ do_insert=1; break; } } /* re-allocate space */ if(ino->attr_count % 8 ==0) { ntfs_attribute* new; new = (ntfs_attribute*)ntfs_malloc((ino->attr_count+8)* sizeof(ntfs_attribute)); if( !new ) return ENOMEM; if( ino->attrs ) { ntfs_memcpy( new, ino->attrs, ino->attr_count*sizeof(ntfs_attribute) ); ntfs_free( ino->attrs ); } ino->attrs = new; } if(do_insert) ntfs_memmove(ino->attrs+i+1,ino->attrs+i,(ino->attr_count-i)* sizeof(ntfs_attribute)); ino->attr_count++; ino->attrs[i].type=type; ino->attrs[i].namelen=namelen; ino->attrs[i].name=name; *pos=i; *found=0; return 0; } int ntfs_make_attr_resident(ntfs_inode *ino,ntfs_attribute *attr) { int size=attr->size; if(size>0){ /* FIXME: read data, free clusters */ return EOPNOTSUPP; } attr->resident=1; return 0; } /* Store in the inode readable information about a run */ void ntfs_insert_run(ntfs_attribute *attr,int cnum,ntfs_cluster_t cluster,int len) { /* (re-)allocate space if necessary */ if(attr->d.r.len % 8 == 0) { ntfs_runlist* new; new = ntfs_malloc((attr->d.r.len+8)*sizeof(ntfs_runlist)); if( !new ) return; if( attr->d.r.runlist ) { ntfs_memcpy(new, attr->d.r.runlist, attr->d.r.len *sizeof(ntfs_runlist)); ntfs_free( attr->d.r.runlist ); } attr->d.r.runlist = new; } if(attr->d.r.len>cnum) ntfs_memmove(attr->d.r.runlist+cnum+1,attr->d.r.runlist+cnum, (attr->d.r.len-cnum)*sizeof(ntfs_runlist)); attr->d.r.runlist[cnum].cluster=cluster; attr->d.r.runlist[cnum].len=len; attr->d.r.len++; } /* Extends an attribute. Another run will be added if necessary, * but we try to extend the last run in the runlist first. * FIXME: what if there isn't enough contiguous space, we don't create * multiple runs? * * *len: the desired new length of the attr (_not_ the amount to extend by) */ int ntfs_extend_attr(ntfs_inode *ino, ntfs_attribute *attr, int *len, int flags) { int error=0; ntfs_runlist *rl; int rlen; ntfs_cluster_t cluster; int clen; if(attr->compressed)return EOPNOTSUPP; if(ino->record_count>1)return EOPNOTSUPP; if(attr->resident) { error = ntfs_make_attr_nonresident(ino,attr); if(error) return error; } if( *len <= attr->allocated ) return 0; /* truely stupid things do sometimes happen */ rl=attr->d.r.runlist; rlen=attr->d.r.len-1; if(rlen>=0) cluster=rl[rlen].cluster+rl[rlen].len; else /* no preference for allocation space */ cluster=0; /* calculate the extra space we need, and round up to multiple of cluster * size to get number of new clusters needed */ clen=( (*len - attr->allocated ) + ino->vol->clustersize - 1 ) / ino->vol->clustersize; if(clen==0) return 0; /* FIXME: try to allocate smaller pieces */ error=ntfs_allocate_clusters(ino->vol,&cluster,&clen, flags|ALLOC_REQUIRE_SIZE); if(error)return error; attr->allocated += clen*ino->vol->clustersize; *len = attr->allocated; /* contiguous chunk */ if(rlen>=0 && cluster==rl[rlen].cluster+rl[rlen].len){ rl[rlen].len += clen; return 0; } ntfs_insert_run(attr,rlen+1,cluster,clen); return 0; } int ntfs_make_attr_nonresident(ntfs_inode *ino, ntfs_attribute *attr) { void *data=attr->d.data; int len=attr->size; int error,alen; ntfs_io io; attr->d.r.len=0; attr->d.r.runlist=0; attr->resident=0; attr->allocated=attr->initialized=0; alen=len; error=ntfs_extend_attr(ino,attr,&alen,ALLOC_REQUIRE_SIZE); if(error)return error;/* FIXME: On error, restore old values */ io.fn_put=ntfs_put; io.fn_get=ntfs_get; io.param=data; io.size=len; io.do_read=0; return ntfs_readwrite_attr(ino,attr,0,&io); } int ntfs_attr_allnonresident(ntfs_inode *ino) { int i, error=0; ntfs_volume *vol = ino->vol; for (i=0; !error && iattr_count; i++) { if (ino->attrs[i].type != vol->at_security_descriptor && ino->attrs[i].type != vol->at_data) continue; error = ntfs_make_attr_nonresident (ino, ino->attrs+i); } return error; } /* Resize the attribute to a newsize */ int ntfs_resize_attr(ntfs_inode *ino, ntfs_attribute *attr, int newsize) { int error=0; int oldsize=attr->size; int clustersize=ino->vol->clustersize; int i,count,newlen,newcount; ntfs_runlist *rl; if(newsize==oldsize) return 0; /* modifying compressed attributes not supported yet */ if(attr->compressed) /* extending is easy: just insert sparse runs */ return EOPNOTSUPP; if(attr->resident){ void *v; if(newsize>ino->vol->mft_recordsize){ error=ntfs_make_attr_nonresident(ino,attr); if(error)return error; return ntfs_resize_attr(ino,attr,newsize); } v=attr->d.data; if(newsize){ attr->d.data=ntfs_malloc(newsize); if(!attr->d.data) { ntfs_free(v); return ENOMEM; } if(newsize>oldsize) ntfs_bzero((char*)attr->d.data+oldsize, newsize-oldsize); ntfs_memcpy((char*)attr->d.data,v,min(newsize,oldsize)); }else attr->d.data=0; ntfs_free(v); attr->size=newsize; return 0; } /* non-resident attribute */ rl=attr->d.r.runlist; if(newsized.r.len;i++){ if((count+rl[i].len)*clustersize>newsize) break; count+=(int)rl[i].len; } newlen=i+1; /* free unused clusters in current run, unless sparse */ newcount=count; if(rl[i].cluster!=MAX_CLUSTER_T){ int rounded=newsize-count*clustersize; rounded=(rounded+clustersize-1)/clustersize; error=ntfs_deallocate_clusters(ino->vol,rl[i].cluster+rounded, (int)rl[i].len-rounded); if(error) return error; /* FIXME: incomplete operation */ rl[i].len=rounded; newcount=count+rounded; } /* free all other runs */ for(i++;id.r.len;i++) if(rl[i].cluster!=MAX_CLUSTER_T){ error=ntfs_deallocate_clusters(ino->vol,rl[i].cluster,(int)rl[i].len); if(error) return error; /* FIXME: incomplete operation */ } /* FIXME? free space for extra runs in memory */ attr->d.r.len=newlen; }else{ newlen=newsize; error=ntfs_extend_attr(ino,attr,&newlen,ALLOC_REQUIRE_SIZE); if(error)return error; /* FIXME: incomplete */ newcount=newlen/clustersize; } /* fill in new sizes */ attr->allocated = newcount*clustersize; attr->size = newsize; /* attr->initialized does not change. */ if(!newsize) error=ntfs_make_attr_resident(ino,attr); return error; } int ntfs_create_attr(ntfs_inode *ino, int anum, char *aname, void *data, int dsize, ntfs_attribute **rattr) { void *name; int namelen; int found,i; int error; ntfs_attribute *attr; if(dsize>ino->vol->mft_recordsize) /* FIXME: non-resident attributes */ return EOPNOTSUPP; if(aname){ namelen=strlen(aname); name=ntfs_malloc(2*namelen); if( !name ) return ENOMEM; ntfs_ascii2uni(name,aname,namelen); }else{ name=0; namelen=0; } error = ntfs_new_attr(ino,anum,name,namelen,&i,&found,1); if( error ) { ntfs_free( name ); return error; } if(found){ ntfs_free(name); return EEXIST; } *rattr=attr=ino->attrs+i; /* allocate a new number. FIXME: Should this happen on inode writeback? FIXME: extensions records not supported */ error=ntfs_allocate_attr_number(ino,&i); if(error) return error; attr->attrno=i; attr->resident=1; attr->compressed=attr->cengine=0; attr->size=attr->allocated=attr->initialized=dsize; /* FIXME: INDEXED information should come from $AttrDef Currently, only file names are indexed */ if(anum==ino->vol->at_file_name){ attr->indexed=1; }else attr->indexed=0; attr->d.data=ntfs_malloc(dsize); if( !attr->d.data ) return ENOMEM; ntfs_memcpy(attr->d.data,data,dsize); return 0; } /* Non-resident attributes are stored in runs (intervals of clusters). * * This function stores in the inode readable information about a non-resident * attribute. */ static int ntfs_process_runs(ntfs_inode *ino,ntfs_attribute* attr,unsigned char *data) { int startvcn,endvcn; int vcn,cnum; ntfs_cluster_t cluster; int len,ctype; startvcn = NTFS_GETU64(data+0x10); endvcn = NTFS_GETU64(data+0x18); /* check whether this chunk really belongs to the end */ for(cnum=0,vcn=0;cnumd.r.len;cnum++) vcn+=attr->d.r.runlist[cnum].len; if(vcn!=startvcn) { ntfs_error("Problem with runlist in extended record\n"); return -1; } if(!endvcn) { endvcn = NTFS_GETU64(data+0x28)-1; /* allocated length */ endvcn /= ino->vol->clustersize; } data=data+NTFS_GETU16(data+0x20); cnum=attr->d.r.len; cluster=0; for(vcn=startvcn; vcn<=endvcn; vcn+=len) { if(ntfs_decompress_run(&data,&len,&cluster,&ctype)) return -1; if(ctype) ntfs_insert_run(attr,cnum,-1,len); else ntfs_insert_run(attr,cnum,cluster,len); cnum++; } return 0; } /* Insert the attribute starting at attr in the inode ino */ int ntfs_insert_attribute(ntfs_inode *ino, unsigned char* attrdata) { int i,found; int type; short int *name; int namelen; void *data; ntfs_attribute *attr; int error; type = NTFS_GETU32(attrdata); namelen = NTFS_GETU8(attrdata+9); /* read the attribute's name if it has one */ if(!namelen) name=0; else { /* 1 Unicode character fits in 2 bytes */ name=ntfs_malloc(2*namelen); if( !name ) return ENOMEM; ntfs_memcpy(name,attrdata+NTFS_GETU16(attrdata+10),2*namelen); } error = ntfs_new_attr(ino,type,name,namelen,&i,&found,1); if( error ) { if( name ) ntfs_free( name ); return error; } /* We can have in one inode two attributes with type 0x00000030 (File Name) and without name */ if(found && /*FIXME*/type!=ino->vol->at_file_name) { ntfs_process_runs(ino,ino->attrs+i,attrdata); return 0; } else if( found ) { /* Don't understand the above, but I know it leaks memory below as it overwrites a found entry without freeing it. So here we call ntfs_new_attr again but this time ask it to always allocate a new entry */ ntfs_new_attr(ino,type,name,namelen,&i,&found,0); } attr=ino->attrs+i; attr->resident=NTFS_GETU8(attrdata+8)==0; attr->compressed=NTFS_GETU16(attrdata+0xC); attr->attrno=NTFS_GETU16(attrdata+0xE); if(attr->resident) { attr->size=NTFS_GETU16(attrdata+0x10); data=attrdata+NTFS_GETU16(attrdata+0x14); attr->d.data = (void*)ntfs_malloc(attr->size); if( !attr->d.data ) return ENOMEM; ntfs_memcpy(attr->d.data,data,attr->size); attr->indexed=NTFS_GETU16(attrdata+0x16); }else{ attr->allocated=NTFS_GETU32(attrdata+0x28); attr->size=NTFS_GETU32(attrdata+0x30); attr->initialized=NTFS_GETU32(attrdata+0x38); attr->cengine=NTFS_GETU16(attrdata+0x22); if(attr->compressed) attr->compsize=NTFS_GETU32(attrdata+0x40); ino->attrs[i].d.r.runlist=0; ino->attrs[i].d.r.len=0; ntfs_process_runs(ino,attr,attrdata); } return 0; } int ntfs_read_zero(ntfs_io *dest,int size) { char *sparse=ntfs_calloc(512); if(!sparse) return ENOMEM; while(size){ int i=min(size,512); dest->fn_put(dest,sparse,i); size-=i; } ntfs_free(sparse); return 0; } /* process compressed attributes */ int ntfs_read_compressed(ntfs_inode *ino, ntfs_attribute *attr, int offset, ntfs_io *dest) { int error=0; int clustersize,l; int s_vcn,rnum,vcn,len,chunk,got,l1,offs1,copied; ntfs_cluster_t cluster,cl1; char *comp=0,*comp1; char *decomp=0; ntfs_io io; ntfs_runlist *rl; l=dest->size; clustersize=ino->vol->clustersize; /* starting cluster of potential chunk there are three situations: a) in a large uncompressible or sparse chunk, s_vcn is in the middle of a run b) s_vcn is right on a run border c) when several runs make a chunk, s_vcn is before the chunks */ s_vcn=offset/clustersize; /* round down to multiple of 16 */ s_vcn &= ~15; rl=attr->d.r.runlist; for(rnum=vcn=0;rnumd.r.len && vcn+rl->len<=s_vcn;rnum++,rl++) vcn+=rl->len; if(rnum==attr->d.r.len){ /* beyond end of file */ /* FIXME: check allocated/initialized */ dest->size=0; return 0; } io.do_read=1; io.fn_put=ntfs_put; io.fn_get=0; cluster=rl->cluster; len=rl->len; copied=0; while(l){ chunk=0; if(cluster==MAX_CLUSTER_T){ /* sparse cluster */ int l1; if((len-(s_vcn-vcn)) & 15) ntfs_error("unexpected sparse chunk size"); l1=chunk = min((vcn+len)*clustersize-offset,l); error = ntfs_read_zero(dest,l1); if(error) goto out; }else if(dest->do_read){ if(!comp){ comp=ntfs_malloc(16*clustersize); if(!comp){ error=ENOMEM; goto out; } } got=0; /* we might need to start in the middle of a run */ cl1=cluster+s_vcn-vcn; comp1=comp; do{ io.param=comp1; l1=min(len-max(s_vcn-vcn,0),16-got); io.size=l1*clustersize; error=ntfs_getput_clusters(ino->vol,cl1,0,&io); if(error)goto out; if(l1+max(s_vcn-vcn,0)==len){ rnum++;rl++; vcn+=len; cluster=cl1=rl->cluster; len=rl->len; } got+=l1; comp1+=l1*clustersize; }while(cluster!=MAX_CLUSTER_T && got<16); /* until empty run */ chunk=16*clustersize; if(cluster!=MAX_CLUSTER_T || got==16) /* uncompressible */ comp1=comp; else{ if(!decomp){ decomp=ntfs_malloc(16*clustersize); if(!decomp){ error=ENOMEM; goto out; } } /* make sure there are null bytes after the last block */ *(ntfs_u32*)comp1=0; ntfs_decompress(decomp,comp,chunk); comp1=decomp; } offs1=offset-s_vcn*clustersize; chunk=min(16*clustersize-offs1,chunk); chunk=min(l,chunk); dest->fn_put(dest,comp1+offs1,chunk); } l-=chunk; copied+=chunk; offset+=chunk; s_vcn=offset/clustersize & ~15; if(l && offset>=((vcn+len)*clustersize)){ rnum++;rl++; vcn+=len; cluster=rl->cluster; len=rl->len; } } out: if(comp)ntfs_free(comp); if(decomp)ntfs_free(decomp); dest->size=copied; return error; } int ntfs_write_compressed(ntfs_inode *ino, ntfs_attribute *attr, int offset, ntfs_io *dest) { return EOPNOTSUPP; } /* * Local variables: * c-file-style: "linux" * End: */