- Timestamp:
- May 29, 2017 5:52:28 PM (8 years ago)
- Location:
- trunk/src/VBox/Runtime/common/zip
- Files:
-
- 3 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/src/VBox/Runtime/common/zip/tar.h
r67123 r67134 89 89 90 90 /** The uniform standard tape archive format magic value. */ 91 #define RTZIPTAR_USTAR_MAGIC "ustar"91 #define RTZIPTAR_USTAR_MAGIC "ustar" 92 92 /** The ustar version string. 93 93 * @remarks The terminator character is not part of the field. */ 94 #define RTZIPTAR_USTAR_VERSION "00"94 #define RTZIPTAR_USTAR_VERSION "00" 95 95 96 96 /** The GNU magic + version value. */ 97 #define RTZIPTAR_ USTAR_GNU_MAGIC"ustar "97 #define RTZIPTAR_GNU_MAGIC "ustar " 98 98 99 99 … … 140 140 141 141 /** 142 * GNU sparse data segment descriptor entry.142 * GNU sparse data segment descriptor. 143 143 */ 144 144 typedef struct RTZIPTARGNUSPARSE … … 150 150 AssertCompileMemberOffset(RTZIPTARGNUSPARSE, offset, 0); 151 151 AssertCompileMemberOffset(RTZIPTARGNUSPARSE, numbytes, 12); 152 /** Pointer to a GNU sparse data segment descriptor. */ 153 typedef RTZIPTARGNUSPARSE *PRTZIPTARGNUSPARSE; 154 /** Pointer to a const GNU sparse data segment descriptor. */ 155 typedef RTZIPTARGNUSPARSE *PCRTZIPTARGNUSPARSE; 152 156 153 157 /** … … 172 176 char atime[12]; 173 177 char ctime[12]; 174 char offset[12]; 175 char longnames[4]; 178 char offset[12]; /**< for multi-volume? */ 179 char longnames[4]; /**< Seems to be unused. */ 176 180 char unused[1]; 177 181 RTZIPTARGNUSPARSE sparse[4]; … … 284 288 char ab[512]; 285 289 /** The standard header. */ 286 RTZIPTARHDRANCIENT Ancient;290 RTZIPTARHDRANCIENT Ancient; 287 291 /** The standard header. */ 288 RTZIPTARHDRPOSIX Posix;292 RTZIPTARHDRPOSIX Posix; 289 293 /** The GNU header. */ 290 RTZIPTARHDRGNU Gnu;294 RTZIPTARHDRGNU Gnu; 291 295 /** The bits common to both GNU and the standard header. */ 292 RTZIPTARHDRCOMMON Common; 296 RTZIPTARHDRCOMMON Common; 297 /** GNU sparse header. */ 298 RTZIPTARHDRGNUSPARSE GnuSparse; 293 299 } RTZIPTARHDR; 294 300 AssertCompileSize(RTZIPTARHDR, 512); -
trunk/src/VBox/Runtime/common/zip/tarcmd.cpp
r67124 r67134 139 139 * Terminated by a NULL entry. */ 140 140 const char * const *papszFiles; 141 142 /** The TAR format to create. */ 143 RTZIPTARFORMAT enmTarFormat; 144 /** TAR creation flags. */ 145 uint32_t fTarCreate; 146 141 147 } RTZIPTARCMDOPS; 142 148 /** Pointer to the IPRT tar options. */ … … 174 180 } 175 181 return false; 182 } 183 184 185 /** 186 * Archives a file. 187 * 188 * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE + printed message. 189 * @param pOpts The options. 190 * @param hVfsFss The TAR filesystem stream handle. 191 * @param pszSrc The file path or VFS spec. 192 * @param pszDst The name to archive the file under. 193 * @param pErrInfo Error info buffer (saves stack space). 194 */ 195 static RTEXITCODE rtZipTarCmdArchiveFile(PRTZIPTARCMDOPS pOpts, RTVFSFSSTREAM hVfsFss, 196 const char *pszSrc, const char *pszDst, PRTERRINFOSTATIC pErrInfo) 197 { 198 if (pOpts->fVerbose) 199 RTPrintf("%s\n", pszDst); 200 201 /* Open the file. */ 202 uint32_t offError; 203 RTVFSIOSTREAM hVfsIos; 204 int rc = RTVfsChainOpenIoStream(pszSrc, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_NONE, 205 &hVfsIos, &offError, RTErrInfoInitStatic(pErrInfo)); 206 if (RT_FAILURE(rc)) 207 return RTVfsChainMsgErrorExitFailure("RTVfsChainOpenIoStream", pszSrc, rc, offError, &pErrInfo->Core); 208 209 /* I/O stream to base object. */ 210 RTVFSOBJ hVfsObj = RTVfsObjFromIoStream(hVfsIos); 211 RTVfsIoStrmRelease(hVfsIos); 212 if (hVfsObj == NIL_RTVFSOBJ) 213 return RTMsgErrorExitFailure("RTVfsObjFromIoStream failed unexpectedly!"); 214 215 /* Add it to the stream. */ 216 rc = RTVfsFsStrmAdd(hVfsFss, pszDst, hVfsObj, 0 /*fFlags*/); 217 RTVfsObjRelease(hVfsObj); 218 219 if (RT_SUCCESS(rc)) 220 { 221 if (rc != VINF_SUCCESS) 222 RTMsgWarning("%Rrc adding '%s'", rc, pszDst); 223 return RTEXITCODE_SUCCESS; 224 } 225 return RTMsgErrorExitFailure("%Rrc adding '%s'", rc, pszDst); 226 } 227 228 229 /** 230 * Archives a directory recursively . 231 * 232 * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE + printed message. 233 * @param pOpts The options. 234 * @param hVfsFss The TAR filesystem stream handle. 235 * @param pszSrc The directory path or VFS spec. We append to the 236 * buffer as we decend. 237 * @param cchSrc The length of the input. 238 * @param pszDst The name to archive it the under. We append to the 239 * buffer as we decend. 240 * @param cchDst The length of the input. 241 * @param pErrInfo Error info buffer (saves stack space). 242 */ 243 static RTEXITCODE rtZipTarCmdArchiveDir(PRTZIPTARCMDOPS pOpts, RTVFSFSSTREAM hVfsFss, char pszSrc[RTPATH_MAX], size_t cchSrc, 244 char pszDst[RTPATH_MAX], size_t cchDst, PRTERRINFOSTATIC pErrInfo) 245 { 246 RT_NOREF(pOpts, hVfsFss, pszSrc, cchSrc, pszDst, cchDst, pErrInfo); 247 return RTMsgErrorExitFailure("Adding directories has not yet been implemented! Sorry."); 248 } 249 250 251 252 /** 253 * Opens the output archive specified by the options. 254 * 255 * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE + printed message. 256 * @param pOpts The options. 257 * @param phVfsFss Where to return the TAR filesystem stream handle. 258 */ 259 static RTEXITCODE rtZipTarCmdOpenOutputArchive(PRTZIPTARCMDOPS pOpts, PRTVFSFSSTREAM phVfsFss) 260 { 261 int rc; 262 263 /* 264 * Open the output file. 265 */ 266 RTVFSIOSTREAM hVfsIos; 267 if ( pOpts->pszFile 268 && strcmp(pOpts->pszFile, "-") != 0) 269 { 270 uint32_t offError = 0; 271 RTERRINFOSTATIC ErrInfo; 272 rc = RTVfsChainOpenIoStream(pOpts->pszFile, RTFILE_O_WRITE | RTFILE_O_DENY_WRITE | RTFILE_O_CREATE_REPLACE, 273 &hVfsIos, &offError, RTErrInfoInitStatic(&ErrInfo)); 274 if (RT_FAILURE(rc)) 275 return RTVfsChainMsgErrorExitFailure("RTVfsChainOpenIoStream", pOpts->pszFile, rc, offError, &ErrInfo.Core); 276 } 277 else 278 { 279 rc = RTVfsIoStrmFromStdHandle(RTHANDLESTD_OUTPUT, 280 RTFILE_O_WRITE | RTFILE_O_DENY_WRITE | RTFILE_O_OPEN, 281 true /*fLeaveOpen*/, 282 &hVfsIos); 283 if (RT_FAILURE(rc)) 284 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to prepare standard output for writing: %Rrc", rc); 285 } 286 287 /* 288 * Pass it thru a compressor? 289 */ 290 RTVFSIOSTREAM hVfsIosComp = NIL_RTVFSIOSTREAM; 291 switch (pOpts->chZipper) 292 { 293 /* no */ 294 case '\0': 295 rc = VINF_SUCCESS; 296 break; 297 298 /* gunzip */ 299 case 'z': 300 rc = RTZipGzipCompressIoStream(hVfsIos, 0 /*fFlags*/, 6, &hVfsIosComp); 301 if (RT_FAILURE(rc)) 302 RTMsgError("Failed to open gzip decompressor: %Rrc", rc); 303 break; 304 305 /* bunzip2 */ 306 case 'j': 307 rc = VERR_NOT_SUPPORTED; 308 RTMsgError("bzip2 is not supported by this build"); 309 break; 310 311 /* bug */ 312 default: 313 rc = VERR_INTERNAL_ERROR_2; 314 RTMsgError("unknown decompression method '%c'", pOpts->chZipper); 315 break; 316 } 317 if (RT_FAILURE(rc)) 318 { 319 RTVfsIoStrmRelease(hVfsIos); 320 return RTEXITCODE_FAILURE; 321 } 322 323 if (hVfsIosComp != NIL_RTVFSIOSTREAM) 324 { 325 RTVfsIoStrmRelease(hVfsIos); 326 hVfsIos = hVfsIosComp; 327 hVfsIosComp = NIL_RTVFSIOSTREAM; 328 } 329 330 /* 331 * Open the filesystem stream creator. 332 */ 333 if ( pOpts->enmFormat == RTZIPTARCMDFORMAT_TAR 334 || pOpts->enmFormat == RTZIPTARCMDFORMAT_AUTO_DEFAULT) 335 rc = RTZipTarFsStreamToIoStream(hVfsIos, pOpts->enmTarFormat, pOpts->fTarCreate, phVfsFss); 336 else 337 rc = VERR_NOT_SUPPORTED; 338 RTVfsIoStrmRelease(hVfsIos); 339 if (RT_FAILURE(rc)) 340 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to open tar filesystem stream: %Rrc", rc); 341 342 return RTEXITCODE_SUCCESS; 343 } 344 345 346 /** 347 * Implements archive creation. 348 * 349 * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE + printed message. 350 * @param pOpts The options. 351 */ 352 static RTEXITCODE rtZipTarCreate(PRTZIPTARCMDOPS pOpts) 353 { 354 /* 355 * Refuse to create empty archive. 356 */ 357 if (pOpts->cFiles == 0) 358 return RTMsgErrorExitFailure("Nothing to archive - refusing to create empty archive!"); 359 360 /* 361 * First open the output file. 362 */ 363 RTVFSFSSTREAM hVfsFss; 364 RTEXITCODE rcExit = rtZipTarCmdOpenOutputArchive(pOpts, &hVfsFss); 365 if (rcExit != RTEXITCODE_SUCCESS) 366 return rcExit; 367 368 /* 369 * Process the input files. 370 */ 371 for (uint32_t iFile = 0; iFile < pOpts->cFiles; iFile++) 372 { 373 const char *pszFile = pOpts->papszFiles[iFile]; 374 375 /* 376 * Construct/copy the source name. 377 */ 378 int rc = VINF_SUCCESS; 379 char szSrc[RTPATH_MAX]; 380 if ( RTPathStartsWithRoot(pszFile) 381 || RTVfsChainIsSpec(pszFile)) 382 rc = RTStrCopy(szSrc, sizeof(szSrc), pszFile); 383 else 384 rc = RTPathJoin(szSrc, sizeof(szSrc), pOpts->pszDirectory ? pOpts->pszDirectory : ".", pOpts->papszFiles[iFile]); 385 if (RT_SUCCESS(rc)) 386 { 387 /* 388 * Construct the archived name. We must strip leading root specifier. 389 */ 390 char *pszFinalPath = NULL; 391 char szDst[RTPATH_MAX]; 392 const char *pszDst = pszFile; 393 if (RTVfsChainIsSpec(pszFile)) 394 { 395 uint32_t offError; 396 rc = RTVfsChainQueryFinalPath(pszFile, &pszFinalPath, &offError); 397 if (RT_SUCCESS(rc)) 398 pszDst = pszFinalPath; 399 else 400 rcExit = RTVfsChainMsgErrorExitFailure("RTVfsChainQueryFinalPath", pszFile, rc, offError, NULL); 401 } 402 if (RT_SUCCESS(rc)) 403 { 404 pszDst = RTPathSkipRootSpec(pszDst); 405 if (*pszDst == '\0') 406 { 407 pszDst = pOpts->pszPrefix ? pOpts->pszPrefix : "."; 408 rc = RTStrCopy(szDst, sizeof(szDst), pszDst); 409 } 410 else 411 { 412 if (pOpts->pszPrefix) 413 rc = RTPathJoin(szDst, sizeof(szDst), pOpts->pszPrefix, pszDst); 414 else 415 rc = RTStrCopy(szDst, sizeof(szDst), pszDst); 416 } 417 if (RT_SUCCESS(rc)) 418 { 419 /* 420 * What kind of object is this? 421 */ 422 RTERRINFOSTATIC ErrInfo; 423 uint32_t offError; 424 RTFSOBJINFO ObjInfo; 425 rc = RTVfsChainQueryInfo(szSrc, &ObjInfo, RTFSOBJATTRADD_UNIX, RTPATH_F_ON_LINK, 426 &offError, RTErrInfoInitStatic(&ErrInfo)); 427 if (RT_SUCCESS(rc)) 428 { 429 RTEXITCODE rcExit2; 430 if (RTFS_IS_DIRECTORY(ObjInfo.Attr.fMode)) 431 rcExit2 = rtZipTarCmdArchiveDir(pOpts, hVfsFss, szSrc, strlen(szSrc), szDst, strlen(szDst), &ErrInfo); 432 else 433 rcExit2 = rtZipTarCmdArchiveFile(pOpts, hVfsFss, szSrc, szDst, &ErrInfo); 434 } 435 else 436 rcExit = RTVfsChainMsgErrorExitFailure("RTVfsChainQueryInfo", pszFile, rc, offError, &ErrInfo.Core); 437 } 438 else 439 rcExit = RTMsgErrorExitFailure("archived file name is too long, skipping '%s' (%s)", pszDst, pszFile); 440 } 441 } 442 else 443 rcExit = RTMsgErrorExitFailure("input file name is too long, skipping '%s'", pszFile); 444 } 445 446 /* 447 * Finalize the archive. 448 */ 449 int rc = RTVfsFsStrmEnd(hVfsFss); 450 if (RT_FAILURE(rc)) 451 rcExit = RTMsgErrorExitFailure("RTVfsFsStrmEnd failed: %Rrc", rc); 452 453 RTVfsFsStrmRelease(hVfsFss); 454 return rcExit; 176 455 } 177 456 … … 908 1187 ); 909 1188 RTPrintf("Basic Options:\n" 910 " -C <dir>, --directory <dir> (-A, - C, -d, -r, -u, -x)\n"1189 " -C <dir>, --directory <dir> (-A, -c, -d, -r, -u, -x)\n" 911 1190 " Sets the base directory for input and output file members.\n" 912 1191 " This does not apply to --file, even if it preceeds it.\n" … … 925 1204 "\n"); 926 1205 RTPrintf("Misc Options:\n" 927 " --owner <uid/username> (-A, - C, -d, -r, -u, -x)\n"1206 " --owner <uid/username> (-A, -c, -d, -r, -u, -x)\n" 928 1207 " Set the owner of extracted and archived files to the user specified.\n" 929 " --group <uid/username> (-A, - C, -d, -r, -u, -x)\n"1208 " --group <uid/username> (-A, -c, -d, -r, -u, -x)\n" 930 1209 " Set the group of extracted and archived files to the group specified.\n" 931 1210 " --utc (-t)\n" 932 1211 " Display timestamps as UTC instead of local time.\n" 1212 " -S, --sparse (-A, -c, -u)\n" 1213 " Detect sparse files and store them (gnu tar extension).\n" 1214 " --format <format> (-A, -c, -u, but also -d, -r, -x)\n" 1215 " The file format:\n" 1216 " auto (gnu tar)\n" 1217 " default (gnu tar)\n" 1218 " tar (gnu tar)" 1219 " gnu (tar v1.13+), " 1220 " ustar (tar POSIX.1-1988), " 1221 " pax (tar POSIX.1-2001),\n" 1222 " xar\n" 1223 " Note! Because XAR/TAR detection isn't implemented yet, it\n" 1224 " is necessary to specifcy --format=xar when reading a\n" 1225 " XAR file. Otherwise this option is only for creation.\n" 933 1226 "\n"); 934 1227 RTPrintf("IPRT Options:\n" 935 " --prefix <dir-prefix> (-A, - C, -d, -r, -u)\n"1228 " --prefix <dir-prefix> (-A, -c, -d, -r, -u)\n" 936 1229 " Directory prefix to give the members added to the archive.\n" 937 " --file-mode-and-mask <octal-mode> (-A, - C, -d, -r, -u, -x)\n"1230 " --file-mode-and-mask <octal-mode> (-A, -c, -d, -r, -u, -x)\n" 938 1231 " Restrict the access mode of regular and special files.\n" 939 " --file-mode-or-mask <octal-mode> (-A, -C, -d, -r, -u, -x)\n"1232 " --file-mode-or-mask <octal-mode> (-A, -c, -d, -r, -u, -x)\n" 940 1233 " Include the given access mode for regular and special files.\n" 941 " --dir-mode-and-mask <octal-mode> (-A, - C, -d, -r, -u, -x)\n"1234 " --dir-mode-and-mask <octal-mode> (-A, -c, -d, -r, -u, -x)\n" 942 1235 " Restrict the access mode of directories.\n" 943 " --dir-mode-or-mask <octal-mode> (-A, -C, -d, -r, -u, -x)\n"1236 " --dir-mode-or-mask <octal-mode> (-A, -c, -d, -r, -u, -x)\n" 944 1237 " Include the given access mode for directories.\n" 945 1238 " --read-ahead (-x)\n" … … 966 1259 { 967 1260 /* operations */ 968 { "--concatenate", 'A', RTGETOPT_REQ_NOTHING },969 { "--catenate", 'A', RTGETOPT_REQ_NOTHING },970 { "--create", 'c', RTGETOPT_REQ_NOTHING },971 { "--diff", 'd', RTGETOPT_REQ_NOTHING },972 { "--compare", 'd', RTGETOPT_REQ_NOTHING },973 { "--append", 'r', RTGETOPT_REQ_NOTHING },974 { "--list", 't', RTGETOPT_REQ_NOTHING },975 { "--update", 'u', RTGETOPT_REQ_NOTHING },976 { "--extract", 'x', RTGETOPT_REQ_NOTHING },977 { "--get", 'x', RTGETOPT_REQ_NOTHING },978 { "--delete", RTZIPTARCMD_OPT_DELETE,RTGETOPT_REQ_NOTHING },1261 { "--concatenate", 'A', RTGETOPT_REQ_NOTHING }, 1262 { "--catenate", 'A', RTGETOPT_REQ_NOTHING }, 1263 { "--create", 'c', RTGETOPT_REQ_NOTHING }, 1264 { "--diff", 'd', RTGETOPT_REQ_NOTHING }, 1265 { "--compare", 'd', RTGETOPT_REQ_NOTHING }, 1266 { "--append", 'r', RTGETOPT_REQ_NOTHING }, 1267 { "--list", 't', RTGETOPT_REQ_NOTHING }, 1268 { "--update", 'u', RTGETOPT_REQ_NOTHING }, 1269 { "--extract", 'x', RTGETOPT_REQ_NOTHING }, 1270 { "--get", 'x', RTGETOPT_REQ_NOTHING }, 1271 { "--delete", RTZIPTARCMD_OPT_DELETE, RTGETOPT_REQ_NOTHING }, 979 1272 980 1273 /* basic options */ 981 { "--directory", 'C', RTGETOPT_REQ_STRING },982 { "--file", 'f', RTGETOPT_REQ_STRING },983 { "--verbose", 'v', RTGETOPT_REQ_NOTHING },984 { "--preserve-permissions", 'p', RTGETOPT_REQ_NOTHING },985 { "--bzip2", 'j', RTGETOPT_REQ_NOTHING },986 { "--gzip", 'z', RTGETOPT_REQ_NOTHING },987 { "--gunzip", 'z', RTGETOPT_REQ_NOTHING },988 { "--ungzip", 'z', RTGETOPT_REQ_NOTHING },1274 { "--directory", 'C', RTGETOPT_REQ_STRING }, 1275 { "--file", 'f', RTGETOPT_REQ_STRING }, 1276 { "--verbose", 'v', RTGETOPT_REQ_NOTHING }, 1277 { "--preserve-permissions", 'p', RTGETOPT_REQ_NOTHING }, 1278 { "--bzip2", 'j', RTGETOPT_REQ_NOTHING }, 1279 { "--gzip", 'z', RTGETOPT_REQ_NOTHING }, 1280 { "--gunzip", 'z', RTGETOPT_REQ_NOTHING }, 1281 { "--ungzip", 'z', RTGETOPT_REQ_NOTHING }, 989 1282 990 1283 /* other options. */ 991 { "--owner", RTZIPTARCMD_OPT_OWNER, RTGETOPT_REQ_STRING }, 992 { "--group", RTZIPTARCMD_OPT_GROUP, RTGETOPT_REQ_STRING }, 993 { "--utc", RTZIPTARCMD_OPT_UTC, RTGETOPT_REQ_NOTHING }, 1284 { "--owner", RTZIPTARCMD_OPT_OWNER, RTGETOPT_REQ_STRING }, 1285 { "--group", RTZIPTARCMD_OPT_GROUP, RTGETOPT_REQ_STRING }, 1286 { "--utc", RTZIPTARCMD_OPT_UTC, RTGETOPT_REQ_NOTHING }, 1287 { "--sparse", 'S', RTGETOPT_REQ_NOTHING }, 1288 { "--format", RTZIPTARCMD_OPT_FORMAT, RTGETOPT_REQ_STRING }, 994 1289 995 1290 /* IPRT extensions */ … … 999 1294 { "--dir-mode-and-mask", RTZIPTARCMD_OPT_DIR_MODE_AND_MASK, RTGETOPT_REQ_UINT32 | RTGETOPT_FLAG_OCT }, 1000 1295 { "--dir-mode-or-mask", RTZIPTARCMD_OPT_DIR_MODE_OR_MASK, RTGETOPT_REQ_UINT32 | RTGETOPT_FLAG_OCT }, 1001 { "--format", RTZIPTARCMD_OPT_FORMAT, RTGETOPT_REQ_STRING },1002 1296 { "--read-ahead", RTZIPTARCMD_OPT_READ_AHEAD, RTGETOPT_REQ_NOTHING }, 1003 1297 }; … … 1025 1319 } 1026 1320 #endif 1321 Opts.enmTarFormat = RTZIPTARFORMAT_DEFAULT; 1322 Opts.fTarCreate = 0; 1027 1323 1028 1324 RTGETOPTUNION ValueUnion; … … 1115 1411 break; 1116 1412 1413 /* GNU */ 1414 case 'S': 1415 Opts.fTarCreate |= RTZIPTAR_C_SPARSE; 1416 break; 1417 1117 1418 /* iprt extensions */ 1118 1419 case RTZIPTARCMD_OPT_PREFIX: … … 1140 1441 case RTZIPTARCMD_OPT_FORMAT: 1141 1442 if (!strcmp(ValueUnion.psz, "auto") || !strcmp(ValueUnion.psz, "default")) 1142 Opts.enmFormat = RTZIPTARCMDFORMAT_AUTO_DEFAULT; 1443 { 1444 Opts.enmFormat = RTZIPTARCMDFORMAT_AUTO_DEFAULT; 1445 Opts.enmTarFormat = RTZIPTARFORMAT_DEFAULT; 1446 } 1143 1447 else if (!strcmp(ValueUnion.psz, "tar")) 1144 Opts.enmFormat = RTZIPTARCMDFORMAT_TAR; 1448 { 1449 Opts.enmFormat = RTZIPTARCMDFORMAT_TAR; 1450 Opts.enmTarFormat = RTZIPTARFORMAT_DEFAULT; 1451 } 1452 else if (!strcmp(ValueUnion.psz, "gnu")) 1453 { 1454 Opts.enmFormat = RTZIPTARCMDFORMAT_TAR; 1455 Opts.enmTarFormat = RTZIPTARFORMAT_GNU; 1456 } 1457 else if (!strcmp(ValueUnion.psz, "ustar")) 1458 { 1459 Opts.enmFormat = RTZIPTARCMDFORMAT_TAR; 1460 Opts.enmTarFormat = RTZIPTARFORMAT_USTAR; 1461 } 1462 else if ( !strcmp(ValueUnion.psz, "posix") 1463 || !strcmp(ValueUnion.psz, "pax")) 1464 { 1465 Opts.enmFormat = RTZIPTARCMDFORMAT_TAR; 1466 Opts.enmTarFormat = RTZIPTARFORMAT_PAX; 1467 } 1145 1468 else if (!strcmp(ValueUnion.psz, "xar")) 1146 Opts.enmFormat = RTZIPTARCMDFORMAT_XAR;1469 Opts.enmFormat = RTZIPTARCMDFORMAT_XAR; 1147 1470 else 1148 1471 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Unknown archive format: '%s'", ValueUnion.psz); … … 1203 1526 return rtZipTarDoWithMembers(&Opts, rtZipTarCmdExtractCallback); 1204 1527 1528 case 'c': 1529 return rtZipTarCreate(&Opts); 1530 1205 1531 case 'A': 1206 case 'c':1207 1532 case 'd': 1208 1533 case 'r': -
trunk/src/VBox/Runtime/common/zip/tarvfswriter.cpp
r67123 r67134 32 32 #include <iprt/zip.h> 33 33 34 #include <iprt/asm.h> 34 35 #include <iprt/assert.h> 35 36 #include <iprt/err.h> … … 46 47 47 48 /********************************************************************************************************************************* 49 * Defined Constants And Macros * 50 *********************************************************************************************************************************/ 51 /** The TAR block size we're using in this implementation. 52 * @remarks Should technically be user configurable, but we don't currently need that. */ 53 #define RTZIPTAR_BLOCKSIZE sizeof(RTZIPTARHDR) 54 55 /** Minimum file size we consider for sparse files. */ 56 #define RTZIPTAR_MIN_SPARSE _64K 57 58 59 /********************************************************************************************************************************* 48 60 * Structures and Typedefs * 49 61 *********************************************************************************************************************************/ 50 62 /** 63 * A data span descriptor in a sparse file. 64 */ 65 typedef struct RTZIPTARSPARSESPAN 66 { 67 /** Byte offset into the file of the data. */ 68 uint64_t off; 69 /** Number of bytes of data, rounded up to a multiple of blocksize. */ 70 uint64_t cb; 71 } RTZIPTARSPARSESPAN; 72 /** Pointer to a data span. */ 73 typedef RTZIPTARSPARSESPAN *PRTZIPTARSPARSESPAN; 74 /** Pointer to a const data span. */ 75 typedef RTZIPTARSPARSESPAN const *PCRTZIPTARSPARSESPAN; 76 77 /** 78 * Chunk of TAR sparse file data spans. 79 */ 80 typedef struct RTZIPTARSPARSECHUNK 81 { 82 /** List entry. */ 83 RTLISTNODE Entry; 84 /** Array of data spans. */ 85 RTZIPTARSPARSESPAN aSpans[63]; 86 } RTZIPTARSPARSECHUNK; 87 AssertCompile(sizeof(RTZIPTARSPARSECHUNK) <= 1024); 88 AssertCompile(sizeof(RTZIPTARSPARSECHUNK) >= 1008); 89 /** Pointer to a chunk of TAR data spans. */ 90 typedef RTZIPTARSPARSECHUNK *PRTZIPTARSPARSECHUNK; 91 /** Pointer to a const chunk of TAR data spans. */ 92 typedef RTZIPTARSPARSECHUNK const *PCRTZIPTARSPARSECHUNK; 93 94 /** 95 * TAR sparse file info. 96 */ 97 typedef struct RTZIPTARSPARSE 98 { 99 /** Number of data bytes (real size). */ 100 uint64_t cbDataSpans; 101 /** Number of data spans. */ 102 uint32_t cDataSpans; 103 /** The index of the next span in the tail chunk (to avoid modulus 63). */ 104 uint32_t iNextSpan; 105 /** Head of the data span chunk list (PRTZIPTARSPARSECHUNK). */ 106 RTLISTANCHOR ChunkHead; 107 } RTZIPTARSPARSE; 108 /** Pointer to TAR sparse file info. */ 109 typedef RTZIPTARSPARSE *PRTZIPTARSPARSE; 110 /** Pointer to a const TAR sparse file info. */ 111 typedef RTZIPTARSPARSE const *PCRTZIPTARSPARSE; 112 113 /** 51 114 * Tar filesystem stream private data. 52 115 */ … … 55 118 /** The output I/O stream. */ 56 119 RTVFSIOSTREAM hVfsIos; 120 /** Non-nil if the output is a file. */ 121 RTVFSFILE hVfsFile; 57 122 58 123 /** The TAR format. */ … … 75 140 76 141 142 /********************************************************************************************************************************* 143 * Internal Functions * 144 *********************************************************************************************************************************/ 145 static int rtZipTarFssWriter_AddFile(PRTZIPTARFSSTREAMWRITER pThis, const char *pszPath, RTVFSIOSTREAM hVfsIos, 146 PCRTFSOBJINFO pObjInfo, const char *pszOwnerNm, const char *pszGroupNm); 147 148 77 149 /** 78 150 * Calculates the header checksum and stores it in the chksum field. … … 87 159 88 160 int rc = RTStrFormatU32(pHdr->Common.chksum, sizeof(pHdr->Common.chksum), iUnsignedChksum, 89 8 /*uBase*/, -1 /*cchWidth*/, sizeof(pHdr->Common.chksum) - 1, RTSTR_F_ZEROPAD );161 8 /*uBase*/, -1 /*cchWidth*/, sizeof(pHdr->Common.chksum) - 1, RTSTR_F_ZEROPAD | RTSTR_F_PRECISION); 90 162 AssertRCReturn(rc, VERR_TAR_NUM_VALUE_TOO_LARGE); 91 163 return VINF_SUCCESS; … … 114 186 if (off < _4G * 2U) 115 187 { 116 int rc = RTStrFormatU64(pach12Field, 12, off, 8 /*uBase*/, -1 /*cchWidth*/, 12 - 1, RTSTR_F_ZEROPAD );188 int rc = RTStrFormatU64(pach12Field, 12, off, 8 /*uBase*/, -1 /*cchWidth*/, 12 - 1, RTSTR_F_ZEROPAD | RTSTR_F_PRECISION); 117 189 AssertRCReturn(rc, rc); 118 190 } … … 127 199 * trouble as they are not picky about what they read. 128 200 */ 201 /** @todo above note is wrong: GNU tar only uses base-256 with the GNU tar 202 * format, i.e. "ustar \0", see create.c line 303 in v1.29. */ 129 203 else 130 204 { … … 192 266 int rc; 193 267 rc = RTStrFormatU32(pThis->aHdrs[0].Common.mode, sizeof(pThis->aHdrs[0].Common.mode), pObjInfo->Attr.fMode & RTFS_UNIX_MASK, 194 8 /*uBase*/, -1 /*cchWidth*/, sizeof(pThis->aHdrs[0].Common.mode) - 1, RTSTR_F_ZEROPAD );268 8 /*uBase*/, -1 /*cchWidth*/, sizeof(pThis->aHdrs[0].Common.mode) - 1, RTSTR_F_ZEROPAD | RTSTR_F_PRECISION); 195 269 AssertRCReturn(rc, VERR_TAR_NUM_VALUE_TOO_LARGE); 196 270 … … 201 275 uint32_t uValue = pObjInfo->Attr.u.Unix.uid != NIL_RTUID ? pObjInfo->Attr.u.Unix.uid : 0; 202 276 rc = RTStrFormatU32(pThis->aHdrs[0].Common.uid, sizeof(pThis->aHdrs[0].Common.uid), uValue, 203 8 /*uBase*/, -1 /*cchWidth*/, sizeof(pThis->aHdrs[0].Common.uid) - 1, RTSTR_F_ZEROPAD );277 8 /*uBase*/, -1 /*cchWidth*/, sizeof(pThis->aHdrs[0].Common.uid) - 1, RTSTR_F_ZEROPAD | RTSTR_F_PRECISION); 204 278 AssertRCReturn(rc, VERR_TAR_NUM_VALUE_TOO_LARGE); 205 279 206 280 uValue = pObjInfo->Attr.u.Unix.gid != NIL_RTUID ? pObjInfo->Attr.u.Unix.gid : 0; 207 281 rc = RTStrFormatU32(pThis->aHdrs[0].Common.gid, sizeof(pThis->aHdrs[0].Common.gid), uValue, 208 8 /*uBase*/, -1 /*cchWidth*/, sizeof(pThis->aHdrs[0].Common.gid) - 1, RTSTR_F_ZEROPAD );282 8 /*uBase*/, -1 /*cchWidth*/, sizeof(pThis->aHdrs[0].Common.gid) - 1, RTSTR_F_ZEROPAD | RTSTR_F_PRECISION); 209 283 AssertRCReturn(rc, VERR_TAR_NUM_VALUE_TOO_LARGE); 210 284 … … 220 294 rc = RTStrFormatU64(pThis->aHdrs[0].Common.mtime, sizeof(pThis->aHdrs[0].Common.mtime), 221 295 RTTimeSpecGetSeconds(&pObjInfo->ModificationTime), 222 8 /*uBase*/, -1 /*cchWidth*/, sizeof(pThis->aHdrs[0].Common.mtime) - 1, RTSTR_F_ZEROPAD );296 8 /*uBase*/, -1 /*cchWidth*/, sizeof(pThis->aHdrs[0].Common.mtime) - 1, RTSTR_F_ZEROPAD | RTSTR_F_PRECISION); 223 297 AssertRCReturn(rc, rc); 224 298 … … 247 321 * Set TAR record magic and version. 248 322 */ 249 memcpy(pThis->aHdrs[0].Common.magic, RT_STR_TUPLE("ustar ")); 250 pThis->aHdrs[0].Common.version[0] = ' '; 251 pThis->aHdrs[0].Common.version[1] = ' '; 323 if (pThis->enmFormat == RTZIPTARFORMAT_GNU) 324 memcpy(pThis->aHdrs[0].Gnu.magic, RTZIPTAR_GNU_MAGIC, sizeof(pThis->aHdrs[0].Gnu.magic)); 325 else if ( pThis->enmFormat == RTZIPTARFORMAT_USTAR 326 || pThis->enmFormat == RTZIPTARFORMAT_PAX) 327 { 328 memcpy(pThis->aHdrs[0].Common.magic, RTZIPTAR_USTAR_MAGIC, sizeof(pThis->aHdrs[0].Common.magic)); 329 memcpy(pThis->aHdrs[0].Common.version, RTZIPTAR_USTAR_VERSION, sizeof(pThis->aHdrs[0].Common.version)); 330 } 331 else 332 AssertFailedReturn(VERR_INTERNAL_ERROR_4); 252 333 253 334 /* … … 265 346 rc = RTStrFormatU32(pThis->aHdrs[0].Common.devmajor, sizeof(pThis->aHdrs[0].Common.devmajor), 266 347 RTDEV_MAJOR(pObjInfo->Attr.u.Unix.Device), 267 8 /*uBase*/, -1 /*cchWidth*/, sizeof(pThis->aHdrs[0].Common.devmajor) - 1, RTSTR_F_ZEROPAD );348 8 /*uBase*/, -1 /*cchWidth*/, sizeof(pThis->aHdrs[0].Common.devmajor) - 1, RTSTR_F_ZEROPAD | RTSTR_F_PRECISION); 268 349 AssertRCReturn(rc, VERR_TAR_NUM_VALUE_TOO_LARGE); 269 350 270 351 rc = RTStrFormatU32(pThis->aHdrs[0].Common.devminor, sizeof(pThis->aHdrs[0].Common.devmajor), 271 352 RTDEV_MINOR(pObjInfo->Attr.u.Unix.Device), 272 8 /*uBase*/, -1 /*cchWidth*/, sizeof(pThis->aHdrs[0].Common.devmajor) - 1, RTSTR_F_ZEROPAD );353 8 /*uBase*/, -1 /*cchWidth*/, sizeof(pThis->aHdrs[0].Common.devmajor) - 1, RTSTR_F_ZEROPAD | RTSTR_F_PRECISION); 273 354 AssertRCReturn(rc, VERR_TAR_NUM_VALUE_TOO_LARGE); 274 355 } 275 356 357 #if 0 /** @todo why doesn't this work? */ 358 /* 359 * Set GNU specific stuff. 360 */ 361 if (pThis->enmFormat == RTZIPTARFORMAT_GNU) 362 { 363 rc = RTStrFormatU64(pThis->aHdrs[0].Gnu.ctime, sizeof(pThis->aHdrs[0].Gnu.ctime), 364 RTTimeSpecGetSeconds(&pObjInfo->ChangeTime), 365 8 /*uBase*/, -1 /*cchWidth*/, sizeof(pThis->aHdrs[0].Gnu.ctime) - 1, RTSTR_F_ZEROPAD | RTSTR_F_PRECISION); 366 AssertRCReturn(rc, rc); 367 368 rc = RTStrFormatU64(pThis->aHdrs[0].Gnu.atime, sizeof(pThis->aHdrs[0].Gnu.atime), 369 RTTimeSpecGetSeconds(&pObjInfo->ChangeTime), 370 8 /*uBase*/, -1 /*cchWidth*/, sizeof(pThis->aHdrs[0].Gnu.atime) - 1, RTSTR_F_ZEROPAD | RTSTR_F_PRECISION); 371 AssertRCReturn(rc, rc); 372 } 373 #endif 374 276 375 /* 277 376 * Finally the checksum. 278 377 */ 378 pThis->cHdrs = 1; 279 379 return rtZipTarFssWriter_ChecksumHdr(&pThis->aHdrs[0]); 280 380 } … … 282 382 283 383 /** 284 * Adds a file to the stream. 384 * Allocates a buffer for transfering file data. 385 * 386 * @note Will use the 3rd TAR header as fallback buffer if we're out of 387 * memory! 388 * 389 * @returns Pointer to buffer (won't ever fail). 390 * @param pThis The TAR writer instance. 391 * @param pcbBuf Where to return the buffer size. This will be a 392 * multiple of the TAR block size. 393 * @param ppvFree Where to return the pointer to pass to RTMemTmpFree 394 * when done with the buffer. 395 * @param cbFile The file size. Used as a buffer size hint. 396 */ 397 static uint8_t *rtZipTarFssWrite_AllocBuf(PRTZIPTARFSSTREAMWRITER pThis, size_t *pcbBuf, void **ppvFree, uint64_t cbObject) 398 { 399 uint8_t *pbBuf; 400 401 /* 402 * If this is a large file, try for a large buffer with 16KB alignment. 403 */ 404 if (cbObject >= _64M) 405 { 406 pbBuf = (uint8_t *)RTMemTmpAlloc(_2M + _16K - 1); 407 if (pbBuf) 408 { 409 *pcbBuf = _2M; 410 *ppvFree = pbBuf; 411 return RT_ALIGN_PT(pbBuf, _16K, uint8_t *); 412 } 413 } 414 /* 415 * 4KB aligned 512KB buffer if larger 512KB or larger. 416 */ 417 else if (cbObject >= _512K) 418 { 419 pbBuf = (uint8_t *)RTMemTmpAlloc(_512K + _4K - 1); 420 if (pbBuf) 421 { 422 *pcbBuf = _512K; 423 *ppvFree = pbBuf; 424 return RT_ALIGN_PT(pbBuf, _4K, uint8_t *); 425 } 426 } 427 /* 428 * Otherwise a 4KB aligned 128KB buffer. 429 */ 430 else 431 { 432 pbBuf = (uint8_t *)RTMemTmpAlloc(_128K + _4K - 1); 433 if (pbBuf) 434 { 435 *pcbBuf = _128K; 436 *ppvFree = pbBuf; 437 return RT_ALIGN_PT(pbBuf, _4K, uint8_t *); 438 } 439 } 440 441 /* 442 * If allocation failed, fallback on a 16KB buffer without any extra alignment. 443 */ 444 pbBuf = (uint8_t *)RTMemTmpAlloc(_16K); 445 if (pbBuf) 446 { 447 *pcbBuf = _16K; 448 *ppvFree = pbBuf; 449 return pbBuf; 450 } 451 452 /* 453 * Final fallback, 512KB buffer using the 3rd header. 454 */ 455 AssertCompile(RT_ELEMENTS(pThis->aHdrs) >= 3); 456 *pcbBuf = sizeof(pThis->aHdrs[2]); 457 *ppvFree = NULL; 458 return (uint8_t *)&pThis->aHdrs[2]; 459 } 460 461 462 /** 463 * Frees the sparse info for a TAR file. 464 * 465 * @param pSparse The sparse info to free. 466 */ 467 static void rtZipTarFssWriter_SparseInfoDestroy(PRTZIPTARSPARSE pSparse) 468 { 469 PRTZIPTARSPARSECHUNK pCur; 470 PRTZIPTARSPARSECHUNK pNext; 471 RTListForEachSafe(&pSparse->ChunkHead, pCur, pNext, RTZIPTARSPARSECHUNK, Entry) 472 RTMemTmpFree(pCur); 473 RTMemTmpFree(pSparse); 474 } 475 476 477 /** 478 * Adds a data span to the sparse info. 479 * 480 * @returns VINF_SUCCESS or VINF_NO_TMP_MEMORY. 481 * @param pSparse The sparse info to free. 482 * @param offSpan Offset of the span. 483 * @param cbSpan Number of bytes. 484 */ 485 static int rtZipTarFssWriter_SparseInfoAddSpan(PRTZIPTARSPARSE pSparse, uint64_t offSpan, uint64_t cbSpan) 486 { 487 /* 488 * Get the chunk we're adding it to. 489 */ 490 PRTZIPTARSPARSECHUNK pChunk; 491 if (pSparse->iNextSpan != 0) 492 { 493 pChunk = RTListGetLast(&pSparse->ChunkHead, RTZIPTARSPARSECHUNK, Entry); 494 Assert(pSparse->iNextSpan < RT_ELEMENTS(pChunk->aSpans)); 495 } 496 else 497 { 498 pChunk = (PRTZIPTARSPARSECHUNK)RTMemTmpAllocZ(sizeof(*pChunk)); 499 if (!pChunk) 500 return VERR_NO_TMP_MEMORY; 501 RTListAppend(&pSparse->ChunkHead, &pChunk->Entry); 502 } 503 504 /* 505 * Append it. 506 */ 507 pSparse->cDataSpans += 1; 508 pSparse->cbDataSpans += cbSpan; 509 pChunk->aSpans[pSparse->iNextSpan].cb = cbSpan; 510 pChunk->aSpans[pSparse->iNextSpan].off = offSpan; 511 if (++pSparse->iNextSpan >= RT_ELEMENTS(pChunk->aSpans)) 512 pSparse->iNextSpan = 0; 513 return VINF_SUCCESS; 514 } 515 516 517 /** 518 * Scans the input stream recording non-zero blocks. 519 */ 520 static int rtZipTarFssWriter_ScanSparseFile(PRTZIPTARFSSTREAMWRITER pThis, RTVFSFILE hVfsFile, uint64_t cbFile, 521 size_t cbBuf, uint8_t *pbBuf, PRTZIPTARSPARSE *ppSparse) 522 { 523 RT_NOREF(pThis); 524 525 /* 526 * Create an empty sparse info bundle. 527 */ 528 PRTZIPTARSPARSE pSparse = (PRTZIPTARSPARSE)RTMemTmpAlloc(sizeof(*pSparse)); 529 AssertReturn(pSparse, VERR_NO_MEMORY); 530 pSparse->cbDataSpans = 0; 531 pSparse->cDataSpans = 0; 532 pSparse->iNextSpan = 0; 533 RTListInit(&pSparse->ChunkHead); 534 535 /* 536 * Scan the file from the start. 537 */ 538 int rc = RTVfsFileSeek(hVfsFile, 0, RTFILE_SEEK_BEGIN, NULL); 539 if (RT_SUCCESS(rc)) 540 { 541 bool fZeroSpan = false; 542 uint64_t offSpan = 0; 543 uint64_t cbSpan = 0; 544 545 for (uint64_t off = 0; off < cbFile;) 546 { 547 uint64_t cbLeft = cbFile - off; 548 size_t cbToRead = cbLeft >= cbBuf ? cbBuf : (size_t)cbLeft; 549 rc = RTVfsFileRead(hVfsFile, pbBuf, cbToRead, NULL); 550 if (RT_FAILURE(rc)) 551 break; 552 size_t cBlocks = cbToRead / RTZIPTAR_BLOCKSIZE; 553 554 /* Zero pad the final buffer to a multiple of the blocksize. */ 555 if (!(cbToRead & (RTZIPTAR_BLOCKSIZE - 1))) 556 { /* likely */ } 557 else 558 { 559 AssertBreakStmt(cbLeft == cbToRead, rc = VERR_INTERNAL_ERROR_3); 560 RT_BZERO(&pbBuf[cbToRead], RTZIPTAR_BLOCKSIZE - (cbToRead & (RTZIPTAR_BLOCKSIZE - 1))); 561 cBlocks++; 562 } 563 564 /* 565 * Process the blocks we've just read one by one. 566 */ 567 uint8_t const *pbBlock = pbBuf; 568 for (size_t iBlock = 0; iBlock < cBlocks; iBlock++) 569 { 570 bool fZeroBlock = ASMMemIsZero(pbBlock, RTZIPTAR_BLOCKSIZE); 571 if (fZeroBlock == fZeroSpan) 572 cbSpan += RTZIPTAR_BLOCKSIZE; 573 else 574 { 575 if (!fZeroSpan && cbSpan) 576 { 577 rc = rtZipTarFssWriter_SparseInfoAddSpan(pSparse, offSpan, cbSpan); 578 if (RT_FAILURE(rc)) 579 break; 580 } 581 fZeroSpan = fZeroBlock; 582 offSpan = off; 583 cbSpan = RTZIPTAR_BLOCKSIZE; 584 } 585 586 /* next block. */ 587 pbBlock += RTZIPTAR_BLOCKSIZE; 588 off += RTZIPTAR_BLOCKSIZE; 589 } 590 } 591 592 /* 593 * Deal with the final span. If we've got zeros thowards the end, we 594 * must add a zero byte data span at the end. 595 */ 596 if (RT_SUCCESS(rc)) 597 { 598 if (!fZeroSpan && cbSpan) 599 { 600 if (cbFile & (RTZIPTAR_BLOCKSIZE - 1)) 601 { 602 Assert(!(cbSpan & (RTZIPTAR_BLOCKSIZE - 1))); 603 cbSpan -= RTZIPTAR_BLOCKSIZE; 604 cbSpan |= cbFile & (RTZIPTAR_BLOCKSIZE - 1); 605 } 606 rc = rtZipTarFssWriter_SparseInfoAddSpan(pSparse, offSpan, cbSpan); 607 } 608 if (RT_SUCCESS(rc)) 609 rc = rtZipTarFssWriter_SparseInfoAddSpan(pSparse, cbFile, 0); 610 } 611 } 612 613 if (RT_SUCCESS(rc)) 614 { 615 /* 616 * Return the file back to the start position before we return so that we 617 * can segue into the regular rtZipTarFssWriter_AddFile without further ado. 618 */ 619 rc = RTVfsFileSeek(hVfsFile, 0, RTFILE_SEEK_BEGIN, NULL); 620 if (RT_SUCCESS(rc)) 621 { 622 *ppSparse = pSparse; 623 return VINF_SUCCESS; 624 } 625 } 626 627 rtZipTarFssWriter_SparseInfoDestroy(pSparse); 628 *ppSparse = NULL; 629 return rc; 630 } 631 632 633 /** 634 * Writes GNU the sparse file headers. 635 * 636 * @returns IPRT status code. 637 * @param pThis The TAR writer instance. 638 * @param pszPath The path to the file. 639 * @param pObjInfo The object information. 640 * @param pszOwnerNm The owner name. 641 * @param pszGroupNm The group name. 642 * @param pSparse The sparse file info. 643 */ 644 static int rtZipTarFssWriter_WriteGnuSparseHeaders(PRTZIPTARFSSTREAMWRITER pThis, const char *pszPath, PCRTFSOBJINFO pObjInfo, 645 const char *pszOwnerNm, const char *pszGroupNm, PCRTZIPTARSPARSE pSparse) 646 { 647 /* 648 * Format the first header. 649 */ 650 int rc = rtZipTarFssWriter_ObjInfoToHdr(pThis, pszPath, pObjInfo, pszOwnerNm, pszGroupNm, RTZIPTAR_TF_GNU_SPARSE); 651 AssertRCReturn(rc, rc); 652 AssertReturn(pThis->cHdrs == 1, VERR_INTERNAL_ERROR_2); 653 654 /* data size. */ 655 rc = rtZipTarFssWriter_FormatOffset(pThis->aHdrs[0].Common.size, pSparse->cbDataSpans); 656 AssertRCReturn(rc, rc); 657 658 /* realsize. */ 659 rc = rtZipTarFssWriter_FormatOffset(pThis->aHdrs[0].Gnu.realsize, pObjInfo->cbObject); 660 AssertRCReturn(rc, rc); 661 662 /* 663 * Walk the sparse spans, fill and write headers one by one. 664 */ 665 PRTZIPTARGNUSPARSE paSparse = &pThis->aHdrs[0].Gnu.sparse[0]; 666 uint32_t cSparse = RT_ELEMENTS(pThis->aHdrs[0].Gnu.sparse); 667 uint32_t iSparse = 0; 668 669 PRTZIPTARSPARSECHUNK const pLastChunk = RTListGetLast(&pSparse->ChunkHead, RTZIPTARSPARSECHUNK, Entry); 670 PRTZIPTARSPARSECHUNK pChunk; 671 RTListForEach(&pSparse->ChunkHead, pChunk, RTZIPTARSPARSECHUNK, Entry) 672 { 673 uint32_t cSpans = pChunk != pLastChunk || pSparse->iNextSpan == 0 674 ? RT_ELEMENTS(pChunk->aSpans) : pSparse->iNextSpan; 675 for (uint32_t iSpan = 0; iSpan < cSpans; iSpan++) 676 { 677 /* Flush the header? */ 678 if (iSparse >= cSparse) 679 { 680 pThis->aHdrs[0].Gnu.isextended = 1; /* more headers to come */ 681 if (cSparse == RT_ELEMENTS(pThis->aHdrs[0].Gnu.sparse)) 682 rc = rtZipTarFssWriter_ChecksumHdr(&pThis->aHdrs[0]); 683 if (RT_SUCCESS(rc)) 684 rc = RTVfsIoStrmWrite(pThis->hVfsIos, &pThis->aHdrs[0], sizeof(pThis->aHdrs[0]), true /*fBlocking*/, NULL); 685 if (RT_FAILURE(rc)) 686 return rc; 687 RT_ZERO(pThis->aHdrs[0]); 688 cSparse = RT_ELEMENTS(pThis->aHdrs[0].GnuSparse.sp); 689 iSparse = 0; 690 paSparse = &pThis->aHdrs[0].GnuSparse.sp[0]; 691 } 692 693 /* Append sparse data segment. */ 694 rc = rtZipTarFssWriter_FormatOffset(paSparse[iSparse].offset, pChunk->aSpans[iSpan].off); 695 AssertRCReturn(rc, rc); 696 rc = rtZipTarFssWriter_FormatOffset(paSparse[iSparse].numbytes, pChunk->aSpans[iSpan].cb); 697 AssertRCReturn(rc, rc); 698 iSparse++; 699 } 700 } 701 702 /* 703 * The final header. 704 */ 705 if (iSparse != 0) 706 { 707 Assert(pThis->aHdrs[0].Gnu.isextended == 0); 708 rc = RTVfsIoStrmWrite(pThis->hVfsIos, &pThis->aHdrs[0], sizeof(pThis->aHdrs[0]), true /*fBlocking*/, NULL); 709 } 710 pThis->cHdrs = 0; 711 return rc; 712 } 713 714 715 /** 716 * Adds a potentially sparse file to the output. 717 * 718 * @returns IPRT status code. 719 * @param pThis The TAR writer instance. 720 * @param pszPath The path to the file. 721 * @param hVfsFile The potentially sparse file. 722 * @param hVfsIos The I/O stream of the file. Same as @a hVfsFile. 723 * @param pObjInfo The object information. 724 * @param pszOwnerNm The owner name. 725 * @param pszGroupNm The group name. 726 */ 727 static int rtZipTarFssWriter_AddFileSparse(PRTZIPTARFSSTREAMWRITER pThis, const char *pszPath, RTVFSFILE hVfsFile, 728 RTVFSIOSTREAM hVfsIos, PCRTFSOBJINFO pObjInfo, 729 const char *pszOwnerNm, const char *pszGroupNm) 730 { 731 /* 732 * Scan the input file to locate all zero blocks. 733 */ 734 void *pvBufFree; 735 size_t cbBuf; 736 uint8_t *pbBuf = rtZipTarFssWrite_AllocBuf(pThis, &cbBuf, &pvBufFree, pObjInfo->cbObject); 737 738 PRTZIPTARSPARSE pSparse; 739 int rc = rtZipTarFssWriter_ScanSparseFile(pThis, hVfsFile, pObjInfo->cbObject, cbBuf, pbBuf, &pSparse); 740 if (RT_SUCCESS(rc)) 741 { 742 /* 743 * If there aren't at least 2 zero blocks in the file, don't bother 744 * doing the sparse stuff and store it as a normal file. 745 */ 746 if (pSparse->cbDataSpans + RTZIPTAR_BLOCKSIZE > (uint64_t)pObjInfo->cbObject) 747 { 748 rtZipTarFssWriter_SparseInfoDestroy(pSparse); 749 RTMemTmpFree(pvBufFree); 750 return rtZipTarFssWriter_AddFile(pThis, pszPath, hVfsIos, pObjInfo, pszOwnerNm, pszGroupNm); 751 } 752 753 /* 754 * Produce and write the headers. 755 */ 756 if (pThis->enmFormat == RTZIPTARFORMAT_GNU) 757 rc = rtZipTarFssWriter_WriteGnuSparseHeaders(pThis, pszPath, pObjInfo, pszOwnerNm, pszGroupNm, pSparse); 758 else 759 AssertStmt(pThis->enmFormat != RTZIPTARFORMAT_GNU, rc = VERR_NOT_IMPLEMENTED); 760 if (RT_SUCCESS(rc)) 761 { 762 /* 763 * Write the file bytes. 764 */ 765 PRTZIPTARSPARSECHUNK const pLastChunk = RTListGetLast(&pSparse->ChunkHead, RTZIPTARSPARSECHUNK, Entry); 766 PRTZIPTARSPARSECHUNK pChunk; 767 RTListForEach(&pSparse->ChunkHead, pChunk, RTZIPTARSPARSECHUNK, Entry) 768 { 769 uint32_t cSpans = pChunk != pLastChunk || pSparse->iNextSpan == 0 770 ? RT_ELEMENTS(pChunk->aSpans) : pSparse->iNextSpan; 771 for (uint32_t iSpan = 0; iSpan < cSpans; iSpan++) 772 { 773 rc = RTVfsFileSeek(hVfsFile, pChunk->aSpans[iSpan].off, RTFILE_SEEK_BEGIN, NULL); 774 if (RT_FAILURE(rc)) 775 break; 776 uint64_t cbLeft = pChunk->aSpans[iSpan].cb; 777 Assert( !(cbLeft & (RTZIPTAR_BLOCKSIZE - 1)) 778 || (iSpan + 1 == cSpans && pChunk == pLastChunk)); 779 while (cbLeft > 0) 780 { 781 size_t cbToRead = cbLeft >= cbBuf ? cbBuf : (size_t)cbLeft; 782 rc = RTVfsFileRead(hVfsFile, pbBuf, cbToRead, NULL); 783 if (RT_SUCCESS(rc)) 784 { 785 rc = RTVfsIoStrmWrite(pThis->hVfsIos, pbBuf, cbToRead, true /*fBlocking*/, NULL); 786 if (RT_SUCCESS(rc)) 787 { 788 pThis->cbWritten += cbToRead; 789 cbLeft -= cbToRead; 790 continue; 791 } 792 } 793 break; 794 } 795 if (RT_FAILURE(rc)) 796 break; 797 } 798 } 799 800 /* 801 * Do the zero padding. 802 */ 803 if ( RT_SUCCESS(rc) 804 && (pSparse->cbDataSpans & (RTZIPTAR_BLOCKSIZE - 1))) 805 { 806 size_t cbToZero = RTZIPTAR_BLOCKSIZE - (pSparse->cbDataSpans & (RTZIPTAR_BLOCKSIZE - 1)); 807 rc = RTVfsIoStrmWrite(pThis->hVfsIos, g_abRTZero4K, cbToZero, true /*fBlocking*/, NULL); 808 if (RT_SUCCESS(rc)) 809 pThis->cbWritten += cbToZero; 810 } 811 } 812 813 if (RT_FAILURE(rc)) 814 pThis->rcFatal = rc; 815 rtZipTarFssWriter_SparseInfoDestroy(pSparse); 816 } 817 RTMemTmpFree(pvBufFree); 818 return rc; 819 } 820 821 822 /** 823 * Adds an I/O stream of indeterminate length to the TAR file. 824 * 825 * This requires the output to be seekable, i.e. a file, because we need to go 826 * back and update @c size field of the TAR header after pumping all the data 827 * bytes thru and establishing the file length. 285 828 * 286 829 * @returns IPRT status code. … … 288 831 * @param pszPath The path to the file. 289 832 * @param hVfsIos The I/O stream of the file. 290 * @param fFlags The RTVFSFSSTREAMOPS::pfnAdd flags.291 833 * @param pObjInfo The object information. 292 834 * @param pszOwnerNm The owner name. 293 835 * @param pszGroupNm The group name. 294 836 */ 295 static int rtZipTarFssWriter_AddFile(PRTZIPTARFSSTREAMWRITER pThis, const char *pszPath, RTVFSIOSTREAM hVfsIos, uint32_t fFlags, 296 PCRTFSOBJINFO pObjInfo, const char *pszOwnerNm, const char *pszGroupNm) 297 { 837 static int rtZipTarFssWriter_AddFileStream(PRTZIPTARFSSTREAMWRITER pThis, const char *pszPath, RTVFSIOSTREAM hVfsIos, 838 PCRTFSOBJINFO pObjInfo, const char *pszOwnerNm, const char *pszGroupNm) 839 { 840 AssertReturn(pThis->hVfsFile != NIL_RTVFSFILE, VERR_NOT_A_FILE); 841 298 842 /* 299 843 * Append the header. … … 302 846 if (RT_SUCCESS(rc)) 303 847 { 304 RTFOFF const offHdr = fFlags & RTVFSFSSTRM_ADD_F_STREAM ? RTVfsIoStrmTell(pThis->hVfsIos) : RTFOFF_MAX; 305 rc = RTVfsIoStrmWrite(pThis->hVfsIos, pThis->aHdrs, pThis->cHdrs * sizeof(pThis->aHdrs[0]), true /*fBlocking*/, NULL); 306 if (RT_SUCCESS(rc)) 307 { 308 pThis->cbWritten += pThis->cHdrs * sizeof(pThis->aHdrs[0]); 309 310 /* 311 * Allocate a tmporary buffer 312 */ 313 uint8_t *pbFree; 314 size_t cbBuf = _512K; 315 uint8_t *pbBuf = pbFree = (uint8_t *)RTMemTmpAlloc(cbBuf); 316 if (!pbBuf) 848 RTFOFF const offHdr = RTVfsFileTell(pThis->hVfsFile); 849 if (offHdr >= 0) 850 { 851 rc = RTVfsIoStrmWrite(pThis->hVfsIos, pThis->aHdrs, pThis->cHdrs * sizeof(pThis->aHdrs[0]), true /*fBlocking*/, NULL); 852 if (RT_SUCCESS(rc)) 317 853 { 318 cbBuf = _32K; 319 pbBuf = pbFree = (uint8_t *)RTMemTmpAlloc(cbBuf); 320 if (!pbBuf) 321 { 322 cbBuf = sizeof(pThis->aHdrs) - sizeof(pThis->aHdrs[0]); 323 pbBuf = (uint8_t *)&pThis->aHdrs[1]; 324 } 325 } 326 327 /* 328 * Copy the bytes. Padding the last buffer to a multiple of 512. 329 */ 330 if (!(fFlags & RTVFSFSSTRM_ADD_F_STREAM)) 331 { 332 uint64_t cbLeft = pObjInfo->cbObject; 333 while (cbLeft > 0) 334 { 335 size_t cbRead = cbLeft > cbBuf ? cbBuf : (size_t)cbBuf; 336 rc = RTVfsIoStrmRead(hVfsIos, pbBuf, cbRead, true /*fBlocking*/, NULL); 337 if (RT_FAILURE(rc)) 338 break; 339 340 size_t cbToWrite = cbRead; 341 if (cbRead & (sizeof(RTZIPTARHDR) - 1)) 342 { 343 size_t cbToZero = sizeof(RTZIPTARHDR) - (cbRead & (sizeof(RTZIPTARHDR) - 1)); 344 memset(&pbBuf[cbRead], 0, cbToZero); 345 cbToWrite += cbToZero; 346 } 347 348 rc = RTVfsIoStrmWrite(pThis->hVfsIos, pbBuf, cbToWrite, true /*fBlocking*/, NULL); 349 if (RT_FAILURE(rc)) 350 break; 351 pThis->cbWritten += cbToWrite; 352 cbLeft -= cbRead; 353 } 354 } 355 /* 356 * Same as above, only here we don't know the exact input length. 357 */ 358 else 359 { 854 pThis->cbWritten += pThis->cHdrs * sizeof(pThis->aHdrs[0]); 855 856 /* 857 * Transfer the bytes. 858 */ 859 void *pvBufFree; 860 size_t cbBuf; 861 uint8_t *pbBuf = rtZipTarFssWrite_AllocBuf(pThis, &cbBuf, &pvBufFree, 862 pObjInfo->cbObject > 0 && pObjInfo->cbObject != RTFOFF_MAX 863 ? pObjInfo->cbObject : _1G); 864 360 865 uint64_t cbReadTotal = 0; 361 866 for (;;) … … 378 883 } 379 884 380 /* Do the zero padding. */ 381 if ((cbReadTotal & (sizeof(RTZIPTARHDR) - 1)) && RT_SUCCESS(rc)) 885 RTMemTmpFree(pvBufFree); 886 887 /* 888 * Do the zero padding. 889 */ 890 if ((cbReadTotal & (RTZIPTAR_BLOCKSIZE - 1)) && RT_SUCCESS(rc)) 382 891 { 383 size_t cbToZero = sizeof(RTZIPTARHDR) - (cbReadTotal & (sizeof(RTZIPTARHDR)- 1));892 size_t cbToZero = RTZIPTAR_BLOCKSIZE - (cbReadTotal & (RTZIPTAR_BLOCKSIZE - 1)); 384 893 rc = RTVfsIoStrmWrite(pThis->hVfsIos, g_abRTZero4K, cbToZero, true /*fBlocking*/, NULL); 385 894 if (RT_SUCCESS(rc)) … … 391 900 * from before the data pumping above and just update the size. 392 901 */ 393 if ( RT_SUCCESS(rc) && (RTFOFF)cbReadTotal != pObjInfo->cbObject)902 if ((RTFOFF)cbReadTotal != pObjInfo->cbObject && RT_SUCCESS(rc)) 394 903 { 395 RT VFSFILE hVfsFile = RTVfsIoStrmToFile(pThis->hVfsIos);396 if ( hVfsFile != NIL_RTVFSFILE)904 RTFOFF const offRestore = RTVfsFileTell(pThis->hVfsFile); 905 if (offRestore >= 0) 397 906 { 398 RTFOFF offRestore = RTVfsFileTell(hVfsFile); 399 if (offRestore >= 0) 907 rc = rtZipTarFssWriter_FormatOffset(pThis->aHdrs[0].Common.size, cbReadTotal); 908 if (RT_SUCCESS(rc)) 909 rc = rtZipTarFssWriter_ChecksumHdr(&pThis->aHdrs[0]); 910 if (RT_SUCCESS(rc)) 400 911 { 401 rc = rtZipTarFssWriter_FormatOffset(pThis->aHdrs[0].Common.size, cbReadTotal);912 rc = RTVfsFileWriteAt(pThis->hVfsFile, offHdr, &pThis->aHdrs[0], sizeof(pThis->aHdrs[0]), NULL); 402 913 if (RT_SUCCESS(rc)) 403 rc = rtZipTarFssWriter_ChecksumHdr(&pThis->aHdrs[0]); 404 if (RT_SUCCESS(rc)) 405 { 406 rc = RTVfsFileWriteAt(hVfsFile, offHdr, &pThis->aHdrs[0], sizeof(pThis->aHdrs[0]), NULL); 407 if (RT_SUCCESS(rc)) 408 rc = RTVfsFileSeek(hVfsFile, offRestore, RTFILE_SEEK_BEGIN, NULL); 409 } 914 rc = RTVfsFileSeek(pThis->hVfsFile, offRestore, RTFILE_SEEK_BEGIN, NULL); 410 915 } 411 else412 rc = (int)offRestore;413 RTVfsFileRelease(hVfsFile);414 916 } 415 917 else 416 AssertFailedStmt(rc = VERR_NOT_A_FILE);918 rc = (int)offRestore; 417 919 } 920 921 if (RT_SUCCESS(rc)) 922 return VINF_SUCCESS; 418 923 } 419 420 RTMemTmpFree(pbFree); 924 } 925 else 926 rc = (int)offHdr; 927 pThis->rcFatal = rc; 928 } 929 return rc; 930 } 931 932 933 /** 934 * Adds a file to the stream. 935 * 936 * @returns IPRT status code. 937 * @param pThis The TAR writer instance. 938 * @param pszPath The path to the file. 939 * @param hVfsIos The I/O stream of the file. 940 * @param fFlags The RTVFSFSSTREAMOPS::pfnAdd flags. 941 * @param pObjInfo The object information. 942 * @param pszOwnerNm The owner name. 943 * @param pszGroupNm The group name. 944 */ 945 static int rtZipTarFssWriter_AddFile(PRTZIPTARFSSTREAMWRITER pThis, const char *pszPath, RTVFSIOSTREAM hVfsIos, 946 PCRTFSOBJINFO pObjInfo, const char *pszOwnerNm, const char *pszGroupNm) 947 { 948 /* 949 * Append the header. 950 */ 951 int rc = rtZipTarFssWriter_ObjInfoToHdr(pThis, pszPath, pObjInfo, pszOwnerNm, pszGroupNm, UINT8_MAX); 952 if (RT_SUCCESS(rc)) 953 { 954 rc = RTVfsIoStrmWrite(pThis->hVfsIos, pThis->aHdrs, pThis->cHdrs * sizeof(pThis->aHdrs[0]), true /*fBlocking*/, NULL); 955 if (RT_SUCCESS(rc)) 956 { 957 pThis->cbWritten += pThis->cHdrs * sizeof(pThis->aHdrs[0]); 958 959 /* 960 * Copy the bytes. Padding the last buffer to a multiple of 512. 961 */ 962 void *pvBufFree; 963 size_t cbBuf; 964 uint8_t *pbBuf = rtZipTarFssWrite_AllocBuf(pThis, &cbBuf, &pvBufFree, pObjInfo->cbObject); 965 966 uint64_t cbLeft = pObjInfo->cbObject; 967 while (cbLeft > 0) 968 { 969 size_t cbRead = cbLeft > cbBuf ? cbBuf : (size_t)cbLeft; 970 rc = RTVfsIoStrmRead(hVfsIos, pbBuf, cbRead, true /*fBlocking*/, NULL); 971 if (RT_FAILURE(rc)) 972 break; 973 974 size_t cbToWrite = cbRead; 975 if (cbRead & (RTZIPTAR_BLOCKSIZE - 1)) 976 { 977 size_t cbToZero = RTZIPTAR_BLOCKSIZE - (cbRead & (RTZIPTAR_BLOCKSIZE - 1)); 978 memset(&pbBuf[cbRead], 0, cbToZero); 979 cbToWrite += cbToZero; 980 } 981 982 rc = RTVfsIoStrmWrite(pThis->hVfsIos, pbBuf, cbToWrite, true /*fBlocking*/, NULL); 983 if (RT_FAILURE(rc)) 984 break; 985 pThis->cbWritten += cbToWrite; 986 cbLeft -= cbRead; 987 } 988 989 RTMemTmpFree(pvBufFree); 990 421 991 if (RT_SUCCESS(rc)) 422 992 return VINF_SUCCESS; … … 533 1103 pThis->hVfsIos = NIL_RTVFSIOSTREAM; 534 1104 1105 if (pThis->hVfsFile != NIL_RTVFSFILE) 1106 { 1107 RTVfsFileRelease(pThis->hVfsFile); 1108 pThis->hVfsFile = NIL_RTVFSFILE; 1109 } 1110 535 1111 return VINF_SUCCESS; 536 1112 } … … 580 1156 581 1157 /* 582 * Do type specific handling. 1158 * Do type specific handling. File have several options and variations to 1159 * take into account, thus the mess. 583 1160 */ 584 1161 if (RTFS_IS_FILE(ObjInfo.Attr.fMode)) … … 586 1163 RTVFSIOSTREAM hVfsIos = RTVfsObjToIoStream(hVfsObj); 587 1164 AssertReturn(hVfsIos != NIL_RTVFSIOSTREAM, VERR_WRONG_TYPE); 588 rc = rtZipTarFssWriter_AddFile(pThis, pszPath, hVfsIos, fFlags, &ObjInfo, 589 ObjOwnerName.Attr.u.UnixOwner.szName, ObjGrpName.Attr.u.UnixOwner.szName); 1165 1166 if (fFlags & RTVFSFSSTRM_ADD_F_STREAM) 1167 rc = rtZipTarFssWriter_AddFileStream(pThis, pszPath, hVfsIos, &ObjInfo, 1168 ObjOwnerName.Attr.u.UnixOwner.szName, ObjGrpName.Attr.u.UnixOwner.szName); 1169 else if ( !(pThis->fFlags & RTZIPTAR_C_SPARSE) 1170 || ObjInfo.cbObject < RTZIPTAR_MIN_SPARSE) 1171 rc = rtZipTarFssWriter_AddFile(pThis, pszPath, hVfsIos, &ObjInfo, 1172 ObjOwnerName.Attr.u.UnixOwner.szName, ObjGrpName.Attr.u.UnixOwner.szName); 1173 else 1174 { 1175 RTVFSFILE hVfsFile = RTVfsObjToFile(hVfsObj); 1176 if (hVfsFile != NIL_RTVFSFILE) 1177 { 1178 rc = rtZipTarFssWriter_AddFileSparse(pThis, pszPath, hVfsFile, hVfsIos, &ObjInfo, 1179 ObjOwnerName.Attr.u.UnixOwner.szName, ObjGrpName.Attr.u.UnixOwner.szName); 1180 RTVfsFileRelease(hVfsFile); 1181 } 1182 else 1183 rc = rtZipTarFssWriter_AddFile(pThis, pszPath, hVfsIos, &ObjInfo, 1184 ObjOwnerName.Attr.u.UnixOwner.szName, ObjGrpName.Attr.u.UnixOwner.szName); 1185 } 590 1186 RTVfsIoStrmRelease(hVfsIos); 591 1187 } … … 612 1208 { 613 1209 PRTZIPTARFSSTREAMWRITER pThis = (PRTZIPTARFSSTREAMWRITER)pvThis; 614 if (RT_SUCCESS(pThis->rcFatal)) 615 { 616 /** @todo investigate zero end records. */ 617 return VINF_SUCCESS; 618 } 619 return pThis->rcFatal; 1210 int rc = pThis->rcFatal; 1211 if (RT_SUCCESS(rc)) 1212 { 1213 /* 1214 * There are supposed to be two zero headers at the end of the archive. 1215 * GNU tar may write more because of the way it does buffering, 1216 * libarchive OTOH writes exactly two. 1217 */ 1218 rc = RTVfsIoStrmWrite(pThis->hVfsIos, g_abRTZero4K, RTZIPTAR_BLOCKSIZE * 2, true /*fBlocking*/, NULL); 1219 if (RT_SUCCESS(rc)) 1220 { 1221 pThis->cbWritten += RTZIPTAR_BLOCKSIZE * 2; 1222 1223 /* 1224 * Flush the output. 1225 */ 1226 rc = RTVfsIoStrmFlush(pThis->hVfsIos); 1227 if (RT_SUCCESS(rc)) 1228 return rc; 1229 } 1230 pThis->rcFatal = rc; 1231 } 1232 return rc; 620 1233 } 621 1234 … … 657 1270 if (enmFormat == RTZIPTARFORMAT_DEFAULT) 658 1271 enmFormat = RTZIPTARFORMAT_GNU; 659 AssertReturn(enmFormat == RTZIPTARFORMAT_GNU, VERR_NOT_IMPLEMENTED); /* Only implementing GNU output at the moment. */ 1272 AssertReturn( enmFormat == RTZIPTARFORMAT_GNU 1273 || enmFormat == RTZIPTARFORMAT_USTAR 1274 , VERR_NOT_IMPLEMENTED); /* Only implementing GNU and USTAR output at the moment. */ 660 1275 661 1276 uint32_t cRefs = RTVfsIoStrmRetain(hVfsIosOut); … … 672 1287 { 673 1288 pThis->hVfsIos = hVfsIosOut; 1289 pThis->hVfsFile = RTVfsIoStrmToFile(hVfsIosOut); 1290 674 1291 pThis->enmFormat = enmFormat; 1292 pThis->fFlags = fFlags; 675 1293 pThis->rcFatal = VINF_SUCCESS; 676 1294
Note:
See TracChangeset
for help on using the changeset viewer.