VirtualBox

source: vbox/trunk/src/VBox/Runtime/r3/isofs.cpp@ 72953

Last change on this file since 72953 was 72953, checked in by vboxsync, 6 years ago

Runtime: Proper error handling (bugref:9188)

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 20.8 KB
Line 
1/* $Id: isofs.cpp 72953 2018-07-07 18:20:59Z vboxsync $ */
2/** @file
3 * IPRT - ISO 9660 file system handling.
4 */
5
6/*
7 * Copyright (C) 2010-2017 Oracle Corporation
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.virtualbox.org. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 *
17 * The contents of this file may alternatively be used under the terms
18 * of the Common Development and Distribution License Version 1.0
19 * (CDDL) only, as it comes in the "COPYING.CDDL" file of the
20 * VirtualBox OSE distribution, in which case the provisions of the
21 * CDDL are applicable instead of those of the GPL.
22 *
23 * You may elect to license modified versions of this file under the
24 * terms and conditions of either the GPL or the CDDL or both.
25 */
26
27
28/*********************************************************************************************************************************
29* Header Files *
30*********************************************************************************************************************************/
31#include <iprt/isofs.h>
32
33#include <iprt/file.h>
34#include <iprt/err.h>
35#include <iprt/mem.h>
36#include <iprt/path.h>
37#include <iprt/string.h>
38
39
40/**
41 * Destroys the path cache.
42 *
43 * @param pFile ISO handle.
44 */
45static void rtIsoFsDestroyPathCache(PRTISOFSFILE pFile)
46{
47 PRTISOFSPATHTABLEENTRY pNode = RTListGetFirst(&pFile->listPaths, RTISOFSPATHTABLEENTRY, Node);
48 while (pNode)
49 {
50 PRTISOFSPATHTABLEENTRY pNext = RTListNodeGetNext(&pNode->Node, RTISOFSPATHTABLEENTRY, Node);
51 bool fLast = RTListNodeIsLast(&pFile->listPaths, &pNode->Node);
52
53 if (pNode->path)
54 RTStrFree(pNode->path);
55 if (pNode->path_full)
56 RTStrFree(pNode->path_full);
57 RTListNodeRemove(&pNode->Node);
58 RTMemFree(pNode);
59
60 if (fLast)
61 break;
62
63 pNode = pNext;
64 }
65}
66
67
68/**
69 * Adds a path entry to the path table list.
70 *
71 * @return IPRT status code.
72 * @param pList Path table list to add the path entry to.
73 * @param pszPath Path to add.
74 * @param pHeader Path header information to add.
75 */
76static int rtIsoFsAddToPathCache(PRTLISTNODE pList, const char *pszPath,
77 RTISOFSPATHTABLEHEADER *pHeader)
78{
79 AssertPtrReturn(pList, VERR_INVALID_PARAMETER);
80 AssertPtrReturn(pszPath, VERR_INVALID_PARAMETER);
81 AssertPtrReturn(pHeader, VERR_INVALID_PARAMETER);
82
83 PRTISOFSPATHTABLEENTRY pNode = (PRTISOFSPATHTABLEENTRY)RTMemAlloc(sizeof(RTISOFSPATHTABLEENTRY));
84 if (pNode == NULL)
85 return VERR_NO_MEMORY;
86
87 pNode->path = NULL;
88 if (RT_SUCCESS(RTStrAAppend(&pNode->path, pszPath)))
89 {
90 memcpy((RTISOFSPATHTABLEHEADER*)&pNode->header,
91 (RTISOFSPATHTABLEHEADER*)pHeader, sizeof(pNode->header));
92
93 pNode->path_full = NULL;
94 pNode->Node.pPrev = NULL;
95 pNode->Node.pNext = NULL;
96 RTListAppend(pList, &pNode->Node);
97 return VINF_SUCCESS;
98 }
99 return VERR_NO_MEMORY;
100}
101
102
103/**
104 * Retrieves the parent path of a given node, assuming that the path table
105 * (still) is in sync with the node's index.
106 *
107 * @return IPRT status code.
108 * @param pList Path table list to use.
109 * @param pNode Node of path table entry to lookup the full path for.
110 * @param pszPathNode Current (partial) parent path; needed for recursion.
111 * @param ppszPath Pointer to a pointer to store the retrieved full path to.
112 */
113static int rtIsoFsGetParentPathSub(PRTLISTNODE pList, PRTISOFSPATHTABLEENTRY pNode,
114 char *pszPathNode, char **ppszPath)
115{
116 int rc = VINF_SUCCESS;
117 /* Do we have a parent? */
118 if (pNode->header.parent_index > 1)
119 {
120 uint16_t idx = 1;
121 /* Get the parent of our current node (pNode) */
122 PRTISOFSPATHTABLEENTRY pNodeParent = RTListGetFirst(pList, RTISOFSPATHTABLEENTRY, Node);
123 while (idx++ < pNode->header.parent_index)
124 pNodeParent = RTListNodeGetNext(&pNodeParent->Node, RTISOFSPATHTABLEENTRY, Node);
125 /* Construct intermediate path (parent + current path). */
126 char *pszPath = RTPathJoinA(pNodeParent->path, pszPathNode);
127 if (pszPath)
128 {
129 /* ... and do the same with the parent's parent until we reached the root. */
130 rc = rtIsoFsGetParentPathSub(pList, pNodeParent, pszPath, ppszPath);
131 RTStrFree(pszPath);
132 }
133 else
134 rc = VERR_NO_STR_MEMORY;
135 }
136 else /* No parent (left), this must be the root path then. */
137 *ppszPath = RTStrDup(pszPathNode);
138 return rc;
139}
140
141
142/**
143 * Updates the path table cache of an ISO file.
144 *
145 * @return IPRT status code.
146 * @param pFile ISO handle.
147 */
148static int rtIsoFsUpdatePathCache(PRTISOFSFILE pFile)
149{
150 AssertPtrReturn(pFile, VERR_INVALID_PARAMETER);
151 rtIsoFsDestroyPathCache(pFile);
152
153 RTListInit(&pFile->listPaths);
154
155 /* Seek to path tables. */
156 int rc = VINF_SUCCESS;
157 Assert(pFile->pvd.path_table_start_first > 16);
158 uint64_t uTableStart = (pFile->pvd.path_table_start_first * RTISOFS_SECTOR_SIZE);
159 Assert(uTableStart % RTISOFS_SECTOR_SIZE == 0); /* Make sure it's aligned. */
160 if (RTFileTell(pFile->file) != uTableStart)
161 rc = RTFileSeek(pFile->file, uTableStart, RTFILE_SEEK_BEGIN, &uTableStart);
162
163 /*
164 * Since this is a sequential format, for performance it's best to read the
165 * complete path table (every entry can have its own level (directory depth) first
166 * and the actual directories of the path table afterwards.
167 */
168
169 /* Read in the path table ... */
170 size_t cbLeft = pFile->pvd.path_table_size;
171 RTISOFSPATHTABLEHEADER header;
172 while ((cbLeft > 0) && RT_SUCCESS(rc))
173 {
174 AssertReturn(cbLeft >= sizeof(RTISOFSPATHTABLEHEADER), VERR_INVALID_PARAMETER);
175 size_t cbRead;
176 rc = RTFileRead(pFile->file, (RTISOFSPATHTABLEHEADER*)&header, sizeof(RTISOFSPATHTABLEHEADER), &cbRead);
177 if (RT_FAILURE(rc) || cbRead == 0)
178 break;
179 cbLeft -= cbRead;
180 if (header.length)
181 {
182 AssertReturn(cbLeft >= header.length, VERR_INVALID_PARAMETER);
183 AssertReturn(header.length <= 31, VERR_INVALID_PARAMETER);
184 /* Allocate and read in the actual path name. */
185 char *pszName = RTStrAlloc(header.length + 1);
186 rc = RTFileRead(pFile->file, (char*)pszName, header.length, &cbRead);
187 if (RT_SUCCESS(rc))
188 {
189 cbLeft -= cbRead;
190 pszName[cbRead] = '\0'; /* Terminate string. */
191 /* Add entry to cache ... */
192 rc = rtIsoFsAddToPathCache(&pFile->listPaths, pszName, &header);
193 }
194 RTStrFree(pszName);
195 /* Read padding if required ... */
196 if ((header.length % 2) != 0) /* If we have an odd length, read/skip the padding byte. */
197 {
198 rc = RTFileSeek(pFile->file, 1, RTFILE_SEEK_CURRENT, NULL);
199 cbLeft--;
200 }
201 }
202 }
203
204 if (RT_SUCCESS(rc))
205 {
206 /* Transform path names into full paths. This is a bit ugly right now. */
207 PRTISOFSPATHTABLEENTRY pNode = RTListGetLast(&pFile->listPaths, RTISOFSPATHTABLEENTRY, Node);
208 while ( pNode
209 && !RTListNodeIsFirst(&pFile->listPaths, &pNode->Node)
210 && RT_SUCCESS(rc))
211 {
212 rc = rtIsoFsGetParentPathSub(&pFile->listPaths, pNode,
213 pNode->path, &pNode->path_full);
214 if (RT_SUCCESS(rc))
215 pNode = RTListNodeGetPrev(&pNode->Node, RTISOFSPATHTABLEENTRY, Node);
216 }
217 }
218
219 return rc;
220}
221
222
223RTR3DECL(int) RTIsoFsOpen(PRTISOFSFILE pFile, const char *pszFileName)
224{
225 AssertPtrReturn(pFile, VERR_INVALID_PARAMETER);
226 AssertPtrReturn(pszFileName, VERR_INVALID_PARAMETER);
227
228 RTListInit(&pFile->listPaths);
229#if 0
230 Assert(sizeof(RTISOFSDATESHORT) == 7);
231 Assert(sizeof(RTISOFSDATELONG) == 17);
232 int l = sizeof(RTISOFSDIRRECORD);
233 RTPrintf("RTISOFSDIRRECORD=%ld\n", l);
234 Assert(l == 33);
235 /* Each volume descriptor exactly occupies one sector. */
236 l = sizeof(RTISOFSPRIVOLDESC);
237 RTPrintf("RTISOFSPRIVOLDESC=%ld\n", l);
238 Assert(l == RTISOFS_SECTOR_SIZE);
239#endif
240 int rc = RTFileOpen(&pFile->file, pszFileName, RTFILE_O_OPEN | RTFILE_O_READ | RTFILE_O_DENY_WRITE);
241 if (RT_SUCCESS(rc))
242 {
243 uint64_t cbSize;
244 rc = RTFileGetSize(pFile->file, &cbSize);
245 if ( RT_SUCCESS(rc)
246 && cbSize > 16 * RTISOFS_SECTOR_SIZE)
247 {
248 uint64_t cbOffset = 16 * RTISOFS_SECTOR_SIZE; /* Start reading at 32k. */
249 size_t cbRead;
250 RTISOFSPRIVOLDESC pvd;
251 bool fFoundPrimary = false;
252 bool fIsValid = false;
253 while (cbOffset < _1M)
254 {
255 /* Get primary descriptor. */
256 rc = RTFileRead(pFile->file, (PRTISOFSPRIVOLDESC)&pvd, sizeof(RTISOFSPRIVOLDESC), &cbRead);
257 if (RT_FAILURE(rc) || cbRead < sizeof(RTISOFSPRIVOLDESC))
258 break;
259 if ( RTStrStr((char*)pvd.name_id, RTISOFS_STANDARD_ID)
260 && pvd.type == 0x1 /* Primary Volume Descriptor */)
261 {
262 memcpy((PRTISOFSPRIVOLDESC)&pFile->pvd,
263 (PRTISOFSPRIVOLDESC)&pvd, sizeof(RTISOFSPRIVOLDESC));
264 fFoundPrimary = true;
265 }
266 else if(pvd.type == 0xff /* Termination Volume Descriptor */)
267 {
268 if (fFoundPrimary)
269 fIsValid = true;
270 break;
271 }
272 cbOffset += sizeof(RTISOFSPRIVOLDESC);
273 }
274
275 if (fIsValid)
276 rc = rtIsoFsUpdatePathCache(pFile);
277 else
278 rc = VERR_INVALID_PARAMETER;
279 }
280 if (RT_FAILURE(rc))
281 RTIsoFsClose(pFile);
282 }
283 return rc;
284}
285
286
287RTR3DECL(void) RTIsoFsClose(PRTISOFSFILE pFile)
288{
289 if (pFile)
290 {
291 rtIsoFsDestroyPathCache(pFile);
292 RTFileClose(pFile->file);
293 }
294}
295
296
297/**
298 * Parses an extent given at the specified sector + size and
299 * searches for a file name to return an allocated directory record.
300 *
301 * @return IPRT status code.
302 * @param pFile ISO handle.
303 * @param pszFileName Absolute file name to search for.
304 * @param uExtentSector Sector of extent.
305 * @param cbExtent Size (in bytes) of extent.
306 * @param ppRec Pointer to a pointer to return the
307 * directory record. Must be free'd with
308 * rtIsoFsFreeDirectoryRecord().
309 */
310static int rtIsoFsFindEntry(PRTISOFSFILE pFile, const char *pszFileName,
311 uint32_t uExtentSector, uint32_t cbExtent /* Bytes */,
312 PRTISOFSDIRRECORD *ppRec)
313{
314 AssertPtrReturn(pFile, VERR_INVALID_PARAMETER);
315 Assert(uExtentSector > 16);
316
317 int rc = RTFileSeek(pFile->file, uExtentSector * RTISOFS_SECTOR_SIZE,
318 RTFILE_SEEK_BEGIN, NULL);
319 if (RT_SUCCESS(rc))
320 {
321 rc = VERR_FILE_NOT_FOUND;
322
323 uint8_t abBuffer[RTISOFS_SECTOR_SIZE];
324 size_t cbLeft = cbExtent;
325 while (!RT_SUCCESS(rc) && cbLeft > 0)
326 {
327 size_t cbRead = 0;
328 int rc2 = RTFileRead(pFile->file, &abBuffer[0], sizeof(abBuffer), &cbRead);
329 AssertRC(rc2);
330 Assert(cbRead == RTISOFS_SECTOR_SIZE);
331 cbLeft -= cbRead;
332
333 uint32_t idx = 0;
334 while (idx < cbRead)
335 {
336 PRTISOFSDIRRECORD pCurRecord = (PRTISOFSDIRRECORD)&abBuffer[idx];
337 if (pCurRecord->record_length == 0)
338 break;
339
340 char *pszName = RTStrAlloc(pCurRecord->name_len + 1);
341 if (RT_UNLIKELY(!pszName))
342 {
343 rc = VERR_NO_STR_MEMORY;
344 break;
345 }
346
347 Assert(idx + sizeof(RTISOFSDIRRECORD) < cbRead);
348 memcpy(pszName, &abBuffer[idx + sizeof(RTISOFSDIRRECORD)], pCurRecord->name_len);
349 pszName[pCurRecord->name_len] = '\0'; /* Force string termination. */
350
351 if ( pCurRecord->name_len == 1
352 && pszName[0] == 0x0)
353 {
354 /* This is a "." directory (self). */
355 }
356 else if ( pCurRecord->name_len == 1
357 && pszName[0] == 0x1)
358 {
359 /* This is a ".." directory (parent). */
360 }
361 else /* Regular directory or file */
362 {
363 if (pCurRecord->flags & RT_BIT(1)) /* Directory */
364 {
365 /* We don't recursively go into directories
366 * because we already have the cached path table. */
367 pszName[pCurRecord->name_len] = 0;
368 /*rc = rtIsoFsParseDir(pFile, pszFileName,
369 pDirHdr->extent_location, pDirHdr->extent_data_length);*/
370 }
371 else /* File */
372 {
373 /* Get last occurrence of ";" and cut it off. */
374 char *pTerm = strrchr(pszName, ';');
375 if (pTerm)
376 pszName[pTerm - pszName] = 0;
377
378 /* Don't use case sensitive comparison here, in IS0 9660 all
379 * file / directory names are UPPERCASE. */
380 if (!RTStrICmp(pszName, pszFileName))
381 {
382 PRTISOFSDIRRECORD pRec = (PRTISOFSDIRRECORD)RTMemAlloc(sizeof(RTISOFSDIRRECORD));
383 if (pRec)
384 {
385 memcpy(pRec, pCurRecord, sizeof(RTISOFSDIRRECORD));
386 *ppRec = pRec;
387 rc = VINF_SUCCESS;
388 }
389 else
390 rc = VERR_NO_MEMORY;
391 break;
392 }
393 }
394 }
395 idx += pCurRecord->record_length;
396 RTStrFree(pszName);
397 }
398 }
399 }
400 return rc;
401}
402
403
404/**
405 * Retrieves the sector of a file extent given by the
406 * full file path within the ISO.
407 *
408 * @return IPRT status code.
409 * @param pFile ISO handle.
410 * @param pszPath File path to resolve.
411 * @param puSector Pointer where to store the found sector to.
412 */
413static int rtIsoFsResolvePath(PRTISOFSFILE pFile, const char *pszPath, uint32_t *puSector)
414{
415 AssertPtrReturn(pFile, VERR_INVALID_PARAMETER);
416 AssertPtrReturn(pszPath, VERR_INVALID_PARAMETER);
417 AssertPtrReturn(puSector, VERR_INVALID_PARAMETER);
418
419 int rc = VERR_FILE_NOT_FOUND;
420 char *pszTemp = RTStrDup(pszPath);
421 if (pszTemp)
422 {
423 RTPathStripFilename(pszTemp);
424
425 bool bFound = false;
426 PRTISOFSPATHTABLEENTRY pNode;
427 if (!RTStrCmp(pszTemp, ".")) /* Root directory? Use first node! */
428 {
429 pNode = RTListGetFirst(&pFile->listPaths, RTISOFSPATHTABLEENTRY, Node);
430 if (pNode)
431 bFound = true;
432 }
433 else
434 {
435 RTListForEach(&pFile->listPaths, pNode, RTISOFSPATHTABLEENTRY, Node)
436 {
437 if ( pNode->path_full != NULL /* Root does not have a path! */
438 && !RTStrICmp(pNode->path_full, pszTemp))
439 {
440 bFound = true;
441 break;
442 }
443 }
444 }
445 if (bFound)
446 {
447 AssertPtr(pNode);
448 *puSector = pNode->header.sector_dir_table;
449 rc = VINF_SUCCESS;
450 }
451 else
452 rc = VERR_FILE_NOT_FOUND;
453 RTStrFree(pszTemp);
454 }
455 else
456 rc = VERR_NO_MEMORY;
457 return rc;
458}
459
460
461/**
462 * Allocates a new directory record.
463 *
464 * @return Pointer to the newly allocated directory record.
465 */
466static PRTISOFSDIRRECORD rtIsoFsCreateDirectoryRecord(void)
467{
468 PRTISOFSDIRRECORD pRecord = (PRTISOFSDIRRECORD)RTMemAlloc(sizeof(RTISOFSDIRRECORD));
469 return pRecord;
470}
471
472
473/**
474 * Frees a previously allocated directory record.
475 *
476 * @return IPRT status code.
477 */
478static void rtIsoFsFreeDirectoryRecord(PRTISOFSDIRRECORD pRecord)
479{
480 RTMemFree(pRecord);
481}
482
483
484/**
485 * Returns an allocated directory record for a given file.
486 *
487 * @return IPRT status code.
488 * @param pFile ISO handle.
489 * @param pszPath File path to resolve.
490 * @param ppRecord Pointer to a pointer to return the
491 * directory record. Must be free'd with
492 * rtIsoFsFreeDirectoryRecord().
493 */
494static int rtIsoFsGetDirectoryRecord(PRTISOFSFILE pFile, const char *pszPath,
495 PRTISOFSDIRRECORD *ppRecord)
496{
497 AssertPtrReturn(pFile, VERR_INVALID_PARAMETER);
498 AssertPtrReturn(pszPath, VERR_INVALID_PARAMETER);
499 AssertPtrReturn(ppRecord, VERR_INVALID_PARAMETER);
500
501 uint32_t uSector;
502 int rc = rtIsoFsResolvePath(pFile, pszPath, &uSector);
503 if (RT_SUCCESS(rc))
504 {
505 /* Seek and read the directory record of given file. */
506 rc = RTFileSeek(pFile->file, uSector * RTISOFS_SECTOR_SIZE,
507 RTFILE_SEEK_BEGIN, NULL);
508 if (RT_SUCCESS(rc))
509 {
510 size_t cbRead;
511 PRTISOFSDIRRECORD pRecord = rtIsoFsCreateDirectoryRecord();
512 if (pRecord)
513 {
514 rc = RTFileRead(pFile->file, (PRTISOFSDIRRECORD)pRecord, sizeof(RTISOFSDIRRECORD), &cbRead);
515 if (RT_SUCCESS(rc))
516 {
517 Assert(cbRead == sizeof(RTISOFSDIRRECORD));
518 *ppRecord = pRecord;
519 }
520 if (RT_FAILURE(rc))
521 rtIsoFsFreeDirectoryRecord(pRecord);
522 }
523 else
524 rc = VERR_NO_MEMORY;
525 }
526 }
527 return rc;
528}
529
530
531RTR3DECL(int) RTIsoFsGetFileInfo(PRTISOFSFILE pFile, const char *pszPath, uint32_t *poffInIso, size_t *pcbLength)
532{
533 AssertPtrReturn(pFile, VERR_INVALID_PARAMETER);
534 AssertPtrReturn(pszPath, VERR_INVALID_PARAMETER);
535 AssertPtrReturn(poffInIso, VERR_INVALID_PARAMETER);
536
537 PRTISOFSDIRRECORD pDirRecord;
538 int rc = rtIsoFsGetDirectoryRecord(pFile, pszPath, &pDirRecord);
539 if (RT_SUCCESS(rc))
540 {
541 /* Get actual file record. */
542 PRTISOFSDIRRECORD pFileRecord = NULL; /* shut up gcc*/
543 rc = rtIsoFsFindEntry(pFile,
544 RTPathFilename(pszPath),
545 pDirRecord->extent_location,
546 pDirRecord->extent_data_length,
547 &pFileRecord);
548 if (RT_SUCCESS(rc))
549 {
550 *poffInIso = pFileRecord->extent_location * RTISOFS_SECTOR_SIZE;
551 *pcbLength = pFileRecord->extent_data_length;
552 rtIsoFsFreeDirectoryRecord(pFileRecord);
553 }
554 rtIsoFsFreeDirectoryRecord(pDirRecord);
555 }
556 return rc;
557}
558
559
560RTR3DECL(int) RTIsoFsExtractFile(PRTISOFSFILE pFile, const char *pszSrcPath, const char *pszDstPath)
561{
562 AssertPtrReturn(pFile, VERR_INVALID_PARAMETER);
563 AssertPtrReturn(pszSrcPath, VERR_INVALID_PARAMETER);
564 AssertPtrReturn(pszDstPath, VERR_INVALID_PARAMETER);
565
566 uint32_t cbOffset;
567 size_t cbLength;
568 int rc = RTIsoFsGetFileInfo(pFile, pszSrcPath, &cbOffset, &cbLength);
569 if (RT_SUCCESS(rc))
570 {
571 rc = RTFileSeek(pFile->file, cbOffset, RTFILE_SEEK_BEGIN, NULL);
572 if (RT_SUCCESS(rc))
573 {
574 RTFILE fileDest;
575 rc = RTFileOpen(&fileDest, pszDstPath, RTFILE_O_CREATE | RTFILE_O_WRITE | RTFILE_O_DENY_WRITE);
576 if (RT_SUCCESS(rc))
577 {
578 size_t cbToRead, cbRead, cbWritten;
579 uint8_t byBuffer[_64K];
580 while ( cbLength > 0
581 && RT_SUCCESS(rc))
582 {
583 cbToRead = RT_MIN(cbLength, _64K);
584 rc = RTFileRead(pFile->file, (uint8_t*)byBuffer, cbToRead, &cbRead);
585 if (RT_FAILURE(rc))
586 break;
587 rc = RTFileWrite(fileDest, (uint8_t*)byBuffer, cbRead, &cbWritten);
588 if (RT_FAILURE(rc))
589 break;
590 cbLength -= cbRead;
591 }
592 RTFileClose(fileDest);
593 }
594 }
595 }
596 return rc;
597}
598
Note: See TracBrowser for help on using the repository browser.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette