1 /* 2 * COPYRIGHT: See COPYRIGHT.TXT 3 * PROJECT: Ext2 File System Driver for Windows >= NT 4 * FILE: ea.c 5 * PROGRAMMER: Matt Wu <mattwu@163.com> Kaho Ng <ngkaho1234@gmail.com> 6 * HOMEPAGE: http://www.ext2fsd.com 7 * UPDATE HISTORY: 8 */ 9 10 /* INCLUDES *****************************************************************/ 11 12 #include "ext2fs.h" 13 #include <linux/ext4_xattr.h> 14 15 #ifdef ALLOC_PRAGMA 16 #pragma alloc_text(PAGE, Ext2QueryEa) 17 #pragma alloc_text(PAGE, Ext2SetEa) 18 #pragma alloc_text(PAGE, Ext2IsEaNameValid) 19 #endif 20 21 // Ea iterator 22 struct EaIterator { 23 // Return only an entry 24 BOOLEAN ReturnSingleEntry; 25 26 // Is the buffer overflowing? 27 BOOL OverFlow; 28 29 // FILE_FULL_EA_INFORMATION output buffer 30 PFILE_FULL_EA_INFORMATION FullEa; 31 PFILE_FULL_EA_INFORMATION LastFullEa; 32 33 // UserBuffer's size 34 ULONG UserBufferLength; 35 36 // Remaining UserBuffer's size 37 ULONG RemainingUserBufferLength; 38 39 // Start scanning from this EA 40 ULONG EaIndex; 41 42 // Next EA index returned by Ext2IterateAllEa 43 ULONG EaIndexCounter; 44 }; 45 46 static int Ext2IterateAllEa(struct ext4_xattr_ref *xattr_ref, struct ext4_xattr_item *item, BOOL is_last) 47 { 48 struct EaIterator *pEaIterator = xattr_ref->iter_arg; 49 ULONG EaEntrySize = 4 + 1 + 1 + 2 + item->name_len + 1 + item->data_size; 50 ASSERT(pEaIterator); 51 if (!is_last && !pEaIterator->ReturnSingleEntry) 52 EaEntrySize = ALIGN_UP(EaEntrySize, ULONG); 53 54 // Start iteration from index specified 55 if (pEaIterator->EaIndexCounter < pEaIterator->EaIndex) { 56 pEaIterator->EaIndexCounter++; 57 return EXT4_XATTR_ITERATE_CONT; 58 } 59 pEaIterator->EaIndexCounter++; 60 61 if (EaEntrySize > pEaIterator->RemainingUserBufferLength) { 62 pEaIterator->OverFlow = TRUE; 63 return EXT4_XATTR_ITERATE_STOP; 64 } 65 pEaIterator->FullEa->NextEntryOffset = 0; 66 pEaIterator->FullEa->Flags = 0; 67 pEaIterator->FullEa->EaNameLength = (UCHAR)item->name_len; 68 pEaIterator->FullEa->EaValueLength = (USHORT)item->data_size; 69 RtlCopyMemory(&pEaIterator->FullEa->EaName[0], 70 item->name, 71 item->name_len); 72 RtlCopyMemory(&pEaIterator->FullEa->EaName[0] + item->name_len + 1, 73 item->data, 74 item->data_size); 75 76 // Link FullEa and LastFullEa together 77 if (pEaIterator->LastFullEa) { 78 pEaIterator->LastFullEa->NextEntryOffset = (ULONG) 79 ((PCHAR)pEaIterator->FullEa - (PCHAR)pEaIterator->LastFullEa); 80 } 81 82 pEaIterator->LastFullEa = pEaIterator->FullEa; 83 pEaIterator->FullEa = (PFILE_FULL_EA_INFORMATION) 84 ((PCHAR)pEaIterator->FullEa + EaEntrySize); 85 pEaIterator->RemainingUserBufferLength -= EaEntrySize; 86 87 if (pEaIterator->ReturnSingleEntry) 88 return EXT4_XATTR_ITERATE_STOP; 89 90 return EXT4_XATTR_ITERATE_CONT; 91 } 92 93 NTSTATUS 94 Ext2QueryEa ( 95 IN PEXT2_IRP_CONTEXT IrpContext 96 ) 97 { 98 PIRP Irp = NULL; 99 PIO_STACK_LOCATION IrpSp; 100 101 PDEVICE_OBJECT DeviceObject; 102 103 PEXT2_VCB Vcb = NULL; 104 PEXT2_FCB Fcb = NULL; 105 PEXT2_CCB Ccb = NULL; 106 PEXT2_MCB Mcb = NULL; 107 108 PUCHAR UserEaList; 109 ULONG UserEaListLength; 110 ULONG UserEaIndex; 111 112 BOOLEAN RestartScan; 113 BOOLEAN ReturnSingleEntry; 114 BOOLEAN IndexSpecified; 115 116 BOOLEAN MainResourceAcquired = FALSE; 117 BOOLEAN XattrRefAcquired = FALSE; 118 119 NTSTATUS Status = STATUS_UNSUCCESSFUL; 120 121 struct ext4_xattr_ref xattr_ref; 122 PCHAR UserBuffer; 123 124 ULONG UserBufferLength = 0; 125 ULONG RemainingUserBufferLength = 0; 126 127 PFILE_FULL_EA_INFORMATION FullEa, LastFullEa = NULL; 128 129 _SEH2_TRY { 130 131 Ccb = IrpContext->Ccb; 132 ASSERT(Ccb != NULL); 133 ASSERT((Ccb->Identifier.Type == EXT2CCB) && 134 (Ccb->Identifier.Size == sizeof(EXT2_CCB))); 135 DeviceObject = IrpContext->DeviceObject; 136 Vcb = (PEXT2_VCB)DeviceObject->DeviceExtension; 137 Fcb = IrpContext->Fcb; 138 Mcb = Fcb->Mcb; 139 Irp = IrpContext->Irp; 140 IrpSp = IoGetCurrentIrpStackLocation(Irp); 141 142 Irp->IoStatus.Information = 0; 143 144 // 145 // Receive input parameter from caller 146 // 147 UserBuffer = Ext2GetUserBuffer(Irp); 148 if (!UserBuffer) { 149 Status = STATUS_INSUFFICIENT_RESOURCES; 150 _SEH2_LEAVE; 151 } 152 UserBufferLength = IrpSp->Parameters.QueryEa.Length; 153 RemainingUserBufferLength = UserBufferLength; 154 UserEaList = IrpSp->Parameters.QueryEa.EaList; 155 UserEaListLength = IrpSp->Parameters.QueryEa.EaListLength; 156 UserEaIndex = IrpSp->Parameters.QueryEa.EaIndex; 157 RestartScan = BooleanFlagOn(IrpSp->Flags, SL_RESTART_SCAN); 158 ReturnSingleEntry = BooleanFlagOn(IrpSp->Flags, SL_RETURN_SINGLE_ENTRY); 159 IndexSpecified = BooleanFlagOn(IrpSp->Flags, SL_INDEX_SPECIFIED); 160 161 if (!Mcb) 162 _SEH2_LEAVE; 163 164 // 165 // We do not allow multiple instance gaining EA access to the same file 166 // 167 if (!ExAcquireResourceExclusiveLite( 168 &Fcb->MainResource, 169 IsFlagOn(IrpContext->Flags, IRP_CONTEXT_FLAG_WAIT))) { 170 Status = STATUS_PENDING; 171 _SEH2_LEAVE; 172 } 173 MainResourceAcquired = TRUE; 174 175 Status = Ext2WinntError(ext4_fs_get_xattr_ref(IrpContext, Vcb, Fcb->Mcb, &xattr_ref)); 176 if (!NT_SUCCESS(Status)) { 177 DbgPrint("ext4_fs_get_xattr_ref() failed!\n"); 178 _SEH2_LEAVE; 179 } 180 181 FullEa = (PFILE_FULL_EA_INFORMATION)UserBuffer; 182 183 XattrRefAcquired = TRUE; 184 185 if (RemainingUserBufferLength) 186 RtlZeroMemory(FullEa, RemainingUserBufferLength); 187 188 if (UserEaList != NULL) { 189 int i = 0; 190 PFILE_GET_EA_INFORMATION GetEa; 191 for (GetEa = (PFILE_GET_EA_INFORMATION)&UserEaList[0]; 192 GetEa < (PFILE_GET_EA_INFORMATION)((PUCHAR)UserEaList 193 + UserEaListLength); 194 GetEa = (GetEa->NextEntryOffset == 0 195 ? (PFILE_GET_EA_INFORMATION)MAXUINT_PTR 196 : (PFILE_GET_EA_INFORMATION)((PUCHAR)GetEa 197 + GetEa->NextEntryOffset))) { 198 199 size_t ItemSize; 200 OEM_STRING Str; 201 ULONG EaEntrySize; 202 BOOL is_last = !GetEa->NextEntryOffset; 203 204 Str.MaximumLength = Str.Length = GetEa->EaNameLength; 205 Str.Buffer = &GetEa->EaName[0]; 206 207 // 208 // At the moment we only need to know whether the item exists 209 // and its size. 210 // 211 Status = Ext2WinntError(ext4_fs_get_xattr(&xattr_ref, 212 EXT4_XATTR_INDEX_USER, 213 Str.Buffer, 214 Str.Length, 215 NULL, 216 0, 217 &ItemSize)); 218 if (!NT_SUCCESS(Status)) 219 continue; 220 221 // 222 // We were not able to locate the name therefore we must 223 // dummy up a entry for the query. The needed Ea size is 224 // the size of the name + 4 (next entry offset) + 1 (flags) 225 // + 1 (name length) + 2 (value length) + the name length + 226 // 1 (null byte) + Data Size. 227 // 228 EaEntrySize = 4 + 1 + 1 + 2 + GetEa->EaNameLength + 1 + ItemSize; 229 if (!is_last) 230 EaEntrySize = ALIGN_UP(EaEntrySize, ULONG); 231 232 if (EaEntrySize > RemainingUserBufferLength) { 233 234 Status = i ? STATUS_BUFFER_OVERFLOW : STATUS_BUFFER_TOO_SMALL; 235 _SEH2_LEAVE; 236 } 237 FullEa->NextEntryOffset = 0; 238 FullEa->Flags = 0; 239 FullEa->EaNameLength = GetEa->EaNameLength; 240 FullEa->EaValueLength = (USHORT)ItemSize; 241 RtlCopyMemory(&FullEa->EaName[0], 242 &GetEa->EaName[0], 243 GetEa->EaNameLength); 244 245 // 246 // This query must succeed, or is guarenteed to succeed 247 // since we are only looking up 248 // an EA entry in a in-memory tree structure. 249 // Otherwise that means someone might be operating on 250 // the xattr_ref without acquiring Inode lock. 251 // 252 ASSERT(NT_SUCCESS(Ext2WinntError( 253 ext4_fs_get_xattr(&xattr_ref, 254 EXT4_XATTR_INDEX_USER, 255 Str.Buffer, 256 Str.Length, 257 &FullEa->EaName[0] + FullEa->EaNameLength + 1, 258 ItemSize, 259 &ItemSize 260 )))); 261 FullEa->EaValueLength = (USHORT)ItemSize; 262 263 // Link FullEa and LastFullEa together 264 if (LastFullEa) 265 LastFullEa->NextEntryOffset = (ULONG)((PCHAR)FullEa - 266 (PCHAR)LastFullEa); 267 268 LastFullEa = FullEa; 269 FullEa = (PFILE_FULL_EA_INFORMATION) 270 ((PCHAR)FullEa + EaEntrySize); 271 RemainingUserBufferLength -= EaEntrySize; 272 i++; 273 } 274 } else if (IndexSpecified) { 275 struct EaIterator EaIterator; 276 // 277 // The user supplied an index into the Ea list. 278 // 279 if (RemainingUserBufferLength) 280 RtlZeroMemory(FullEa, RemainingUserBufferLength); 281 282 EaIterator.OverFlow = FALSE; 283 EaIterator.RemainingUserBufferLength = UserBufferLength; 284 // In this case, return only an entry. 285 EaIterator.ReturnSingleEntry = TRUE; 286 EaIterator.FullEa = (PFILE_FULL_EA_INFORMATION)UserBuffer; 287 EaIterator.LastFullEa = NULL; 288 EaIterator.UserBufferLength = UserBufferLength; 289 EaIterator.EaIndex = UserEaIndex; 290 EaIterator.EaIndexCounter = 1; 291 292 xattr_ref.iter_arg = &EaIterator; 293 ext4_fs_xattr_iterate(&xattr_ref, Ext2IterateAllEa); 294 295 RemainingUserBufferLength = EaIterator.RemainingUserBufferLength; 296 297 Status = STATUS_SUCCESS; 298 299 // It seems that the item isn't found 300 if (RemainingUserBufferLength == UserBufferLength) 301 Status = STATUS_OBJECTID_NOT_FOUND; 302 303 if (EaIterator.OverFlow) { 304 if (RemainingUserBufferLength == UserBufferLength) 305 Status = STATUS_BUFFER_TOO_SMALL; 306 else 307 Status = STATUS_BUFFER_OVERFLOW; 308 } 309 310 } else { 311 struct EaIterator EaIterator; 312 // 313 // Else perform a simple scan, taking into account the restart 314 // flag and the position of the next Ea stored in the Ccb. 315 // 316 if (RestartScan) 317 Ccb->EaIndex = 1; 318 319 if (RemainingUserBufferLength) 320 RtlZeroMemory(FullEa, RemainingUserBufferLength); 321 322 EaIterator.OverFlow = FALSE; 323 EaIterator.RemainingUserBufferLength = UserBufferLength; 324 EaIterator.ReturnSingleEntry = ReturnSingleEntry; 325 EaIterator.FullEa = (PFILE_FULL_EA_INFORMATION)UserBuffer; 326 EaIterator.LastFullEa = NULL; 327 EaIterator.UserBufferLength = UserBufferLength; 328 EaIterator.EaIndex = Ccb->EaIndex; 329 EaIterator.EaIndexCounter = 1; 330 331 xattr_ref.iter_arg = &EaIterator; 332 ext4_fs_xattr_iterate(&xattr_ref, Ext2IterateAllEa); 333 334 RemainingUserBufferLength = EaIterator.RemainingUserBufferLength; 335 336 if (Ccb->EaIndex < EaIterator.EaIndexCounter) 337 Ccb->EaIndex = EaIterator.EaIndexCounter; 338 339 Status = STATUS_SUCCESS; 340 341 if (EaIterator.OverFlow) { 342 if (RemainingUserBufferLength == UserBufferLength) 343 Status = STATUS_BUFFER_TOO_SMALL; 344 else 345 Status = STATUS_BUFFER_OVERFLOW; 346 } 347 348 } 349 } 350 _SEH2_FINALLY { 351 352 if (XattrRefAcquired) { 353 if (!NT_SUCCESS(Status)) { 354 xattr_ref.dirty = FALSE; 355 ext4_fs_put_xattr_ref(&xattr_ref); 356 } 357 else 358 Status = Ext2WinntError(ext4_fs_put_xattr_ref(&xattr_ref)); 359 } 360 361 if (MainResourceAcquired) { 362 ExReleaseResourceLite(&Fcb->MainResource); 363 } 364 365 if (NT_SUCCESS(Status)) { 366 Ext2NotifyReportChange( 367 IrpContext, 368 Vcb, 369 Mcb, 370 FILE_NOTIFY_CHANGE_EA, 371 FILE_ACTION_MODIFIED); 372 Irp->IoStatus.Information = UserBufferLength - RemainingUserBufferLength; 373 } 374 375 if (!_SEH2_AbnormalTermination()) { 376 if (Status == STATUS_PENDING || Status == STATUS_CANT_WAIT) { 377 Status = Ext2QueueRequest(IrpContext); 378 } 379 else { 380 Ext2CompleteIrpContext(IrpContext, Status); 381 } 382 } 383 } _SEH2_END; 384 385 return Status; 386 } 387 388 BOOLEAN 389 Ext2IsEaNameValid( 390 IN OEM_STRING Name 391 ) 392 { 393 ULONG Index; 394 UCHAR Char; 395 396 // 397 // Empty names are not valid 398 // 399 400 if (Name.Length == 0) 401 return FALSE; 402 403 // 404 // Do not allow EA name longer than 255 bytes 405 // 406 if (Name.Length > 255) 407 return FALSE; 408 409 for (Index = 0; Index < (ULONG)Name.Length; Index += 1) { 410 411 Char = Name.Buffer[Index]; 412 413 // 414 // Skip over and Dbcs chacters 415 // 416 if (FsRtlIsLeadDbcsCharacter(Char)) { 417 418 ASSERT(Index != (ULONG)(Name.Length - 1)); 419 Index += 1; 420 continue; 421 } 422 423 // 424 // Make sure this character is legal, and if a wild card, that 425 // wild cards are permissible. 426 // 427 if (!FsRtlIsAnsiCharacterLegalFat(Char, FALSE)) 428 return FALSE; 429 430 } 431 432 return TRUE; 433 } 434 435 NTSTATUS 436 Ext2SetEa ( 437 IN PEXT2_IRP_CONTEXT IrpContext 438 ) 439 { 440 PIRP Irp = NULL; 441 PIO_STACK_LOCATION IrpSp; 442 443 PDEVICE_OBJECT DeviceObject; 444 445 PEXT2_VCB Vcb = NULL; 446 PEXT2_FCB Fcb = NULL; 447 PEXT2_CCB Ccb = NULL; 448 PEXT2_MCB Mcb = NULL; 449 450 BOOLEAN MainResourceAcquired = FALSE; 451 BOOLEAN FcbLockAcquired = FALSE; 452 BOOLEAN XattrRefAcquired = FALSE; 453 454 NTSTATUS Status = STATUS_UNSUCCESSFUL; 455 456 struct ext4_xattr_ref xattr_ref; 457 PCHAR UserBuffer; 458 ULONG UserBufferLength; 459 460 PFILE_FULL_EA_INFORMATION FullEa; 461 462 _SEH2_TRY { 463 464 Ccb = IrpContext->Ccb; 465 ASSERT(Ccb != NULL); 466 ASSERT((Ccb->Identifier.Type == EXT2CCB) && 467 (Ccb->Identifier.Size == sizeof(EXT2_CCB))); 468 DeviceObject = IrpContext->DeviceObject; 469 Vcb = (PEXT2_VCB)DeviceObject->DeviceExtension; 470 Fcb = IrpContext->Fcb; 471 Mcb = Fcb->Mcb; 472 Irp = IrpContext->Irp; 473 IrpSp = IoGetCurrentIrpStackLocation(Irp); 474 475 Irp->IoStatus.Information = 0; 476 477 // 478 // Receive input parameter from caller 479 // 480 UserBufferLength = IrpSp->Parameters.SetEa.Length; 481 UserBuffer = Irp->UserBuffer; 482 483 // Check if the EA buffer provided is valid 484 Status = IoCheckEaBufferValidity((PFILE_FULL_EA_INFORMATION)UserBuffer, 485 UserBufferLength, 486 (PULONG)&Irp->IoStatus.Information); 487 if (!NT_SUCCESS(Status)) 488 _SEH2_LEAVE; 489 490 ExAcquireResourceExclusiveLite(&Vcb->FcbLock, TRUE); 491 FcbLockAcquired = TRUE; 492 493 if (!Mcb) 494 _SEH2_LEAVE; 495 496 // 497 // We do not allow multiple instance gaining EA access to the same file 498 // 499 if (!ExAcquireResourceExclusiveLite( 500 &Fcb->MainResource, 501 IsFlagOn(IrpContext->Flags, IRP_CONTEXT_FLAG_WAIT))) { 502 Status = STATUS_PENDING; 503 _SEH2_LEAVE; 504 } 505 MainResourceAcquired = TRUE; 506 507 Status = Ext2WinntError(ext4_fs_get_xattr_ref(IrpContext, Vcb, Fcb->Mcb, &xattr_ref)); 508 if (!NT_SUCCESS(Status)) { 509 DbgPrint("ext4_fs_get_xattr_ref() failed!\n"); 510 _SEH2_LEAVE; 511 } 512 513 XattrRefAcquired = TRUE; 514 515 // 516 // Remove all existing EA entries. 517 // 518 ext4_xattr_purge_items(&xattr_ref); 519 xattr_ref.dirty = TRUE; 520 Status = STATUS_SUCCESS; 521 522 // Iterate the whole EA buffer to do inspection 523 for (FullEa = (PFILE_FULL_EA_INFORMATION)UserBuffer; 524 FullEa < (PFILE_FULL_EA_INFORMATION)&UserBuffer[UserBufferLength]; 525 FullEa = (PFILE_FULL_EA_INFORMATION)(FullEa->NextEntryOffset == 0 ? 526 &UserBuffer[UserBufferLength] : 527 (PCHAR)FullEa + FullEa->NextEntryOffset)) { 528 529 OEM_STRING EaName; 530 531 EaName.MaximumLength = EaName.Length = FullEa->EaNameLength; 532 EaName.Buffer = &FullEa->EaName[0]; 533 534 // Check if EA's name is valid 535 if (!Ext2IsEaNameValid(EaName)) { 536 Irp->IoStatus.Information = (PCHAR)FullEa - UserBuffer; 537 Status = STATUS_INVALID_EA_NAME; 538 _SEH2_LEAVE; 539 } 540 } 541 542 // Now add EA entries to the inode 543 for (FullEa = (PFILE_FULL_EA_INFORMATION)UserBuffer; 544 FullEa < (PFILE_FULL_EA_INFORMATION)&UserBuffer[UserBufferLength]; 545 FullEa = (PFILE_FULL_EA_INFORMATION)(FullEa->NextEntryOffset == 0 ? 546 &UserBuffer[UserBufferLength] : 547 (PCHAR)FullEa + FullEa->NextEntryOffset)) { 548 549 int ret; 550 OEM_STRING EaName; 551 552 EaName.MaximumLength = EaName.Length = FullEa->EaNameLength; 553 EaName.Buffer = &FullEa->EaName[0]; 554 555 Status = Ext2WinntError(ret = 556 ext4_fs_set_xattr_ordered(&xattr_ref, 557 EXT4_XATTR_INDEX_USER, 558 EaName.Buffer, 559 EaName.Length, 560 &FullEa->EaName[0] + FullEa->EaNameLength + 1, 561 FullEa->EaValueLength)); 562 if (!NT_SUCCESS(Status)) 563 _SEH2_LEAVE; 564 565 } 566 } _SEH2_FINALLY { 567 568 if (XattrRefAcquired) { 569 if (!NT_SUCCESS(Status)) { 570 xattr_ref.dirty = FALSE; 571 ext4_fs_put_xattr_ref(&xattr_ref); 572 } else 573 Status = Ext2WinntError(ext4_fs_put_xattr_ref(&xattr_ref)); 574 } 575 576 if (FcbLockAcquired) { 577 ExReleaseResourceLite(&Vcb->FcbLock); 578 FcbLockAcquired = FALSE; 579 } 580 581 if (MainResourceAcquired) { 582 ExReleaseResourceLite(&Fcb->MainResource); 583 } 584 585 if (NT_SUCCESS(Status)) { 586 Ext2NotifyReportChange( 587 IrpContext, 588 Vcb, 589 Mcb, 590 FILE_NOTIFY_CHANGE_EA, 591 FILE_ACTION_MODIFIED); 592 } 593 594 if (!_SEH2_AbnormalTermination()) { 595 if (Status == STATUS_PENDING || Status == STATUS_CANT_WAIT) { 596 Status = Ext2QueueRequest(IrpContext); 597 } 598 else { 599 Ext2CompleteIrpContext(IrpContext, Status); 600 } 601 } 602 } _SEH2_END; 603 return Status; 604 } 605