markdown:
Giới thiệu
Góp ý
Liên hệ quảng cáo
RSS
Đăng ký
Đăng Nhập
Thể Thao
Nhận định
Livescore
Lịch
Kết quả
BXH
Kèo
Xem
Video
Lịch TV
TIP
Dịch vụ
Bóng đá
Phong độ đội bóng Maldives U16
Kết quả Vòng loại U16 Châu Á
- Vòng 3
Palestine U16
FT
3 - 0
(3-0)
Maldives U16
Nhận định
Phong độ
Tường thuật
Trực tuyến
Palestine U16
L
L
L
L
W
20/09/2015
Palestine U16
3-0
Maldives U16
18/09/2015
Palestine U16
0-6
Uzbekistan U16
16/09/2015
Yemen U16
2-0
Palestine U16
27/09/2013
Uzbekistan U16
9-0
Palestine U16
25/09/2013
Yemen U16
3-1
Palestine U16
Sân nhà
L
W
20/09/2015
Palestine U16
3-0
Maldives U16
18/09/2015
Palestine U16
0-6
Uzbekistan U16
Maldives U16
L
W
L
20/09/2015
Palestine U16
3-0
Maldives U16
18/09/2015
Yemen U16
0-6
Maldives U16
16/09/2015
Uzbekistan U16
8-0
Maldives U16
Sân khách
L
W
L
20/09/2015
Palestine U16
3-0
Maldives U16
18/09/2015
Yemen U16
0-6
Maldives U16
16/09/2015
Uzbekistan U16
8-0
Maldives U16
Phong độ đối đầu
20/09/2015
Palestine U16
3-0
Maldives U16
    Palestine U16 100% thắng
Maldives U16 0% thắng
Video bóng đá
Real tung trailer phim cực đỉnh về chiến tích Undecima
Các bàn thắng của U19 Việt Nam tại vòng bảng U19 châu Á
De Gea, Buffon và những pha cứu thua đỉnh nhất tuần qua
Hamburger 0-3 Ein.Frankfurt (Vòng 8 Bundesliga 2016/17)
Osasuna 1-2 Real Betis (Vòng 9 La Liga 2016/17)
Tin Đọc nhiều
1
Conte và Mourinho hâm nóng đại chiến Chelsea - MU
2
Điểm tin sáng 22/10: Ông lớn Premier League tranh giành sao Napoli
3
QBV FIFA 2016: Vẫn chỉ là cuộc chiến giữa Messi và Ronaldo
4
Tân binh của Arsenal muốn được thi đấu nhiều hơn
5
Neymar: 'Chơi cho Barca là việc khá dễ dàng'
6
Huyền thoại MU muốn Modric khoác áo Quỷ đỏ
Loading...

X-XSS-Protection: 1; mode=block

X-Content-Type-Options: nosniff

X-UA-Device: bot

Vary: Accept-Encoding, User-Agent

Via: 1.1 varnish-v4


markdown:
/* * Copyright (c) 1999-2009 Apple Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * * The contents of this file constitute Original Code as defined in and * are subject to the Apple Public Source License Version 1.2 (the * "License"). You may not use this file except in compliance with the * License. Please obtain a copy of the License at *
[http://www.apple.com/publicsource]and read it before using this file. * * This Original Code and all software distributed under the License are * distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT. Please see the * License for the specific language governing rights and limitations * under the License. * * @APPLE_LICENSE_HEADER_END@ */ /* Copyright (c) 1987-99 Apple Computer, Inc. All Rights Reserved. About hfs.util.m: Contains code to implement hfs utility used by the WorkSpace to mount HFS. Change History: 5-Jan-1999 Don Brady Write hfs.label names in UTF-8. 10-Dec-1998 Pat Dirks Changed to try built-in hfs filesystem first. 3-Sep-1998 Don Brady Disable the daylight savings time stuff. 28-Aug-1998 chw Fixed parse args and verify args to indicate that the flags (fixed or removable) are required in the probe case. 22-Jun-1998 Pat Dirks Changed HFSToUFSStr table to switch ":" and "/". 13-Jan-1998 jwc first cut (derived from old NextStep macfs.util code and cdrom.util code). */ /* ************************************** I N C L U D E S ***************************************** */ #include <sys/types.h> #include <sys/stat.h> #include <sys/time.h> #include <sys/sysctl.h> #include <sys/resource.h> #include <sys/vmmeter.h> #include <sys/mount.h> #include <sys/wait.h> #include <sys/param.h> #include <sys/ucred.h> #include <sys/disk.h> #include <sys/loadable_fs.h> #include <sys/attr.h> #include <hfs/hfs_format.h> #include <hfs/hfs_mount.h> #include <ctype.h> #include <errno.h> #include <fcntl.h> #include <pwd.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <syslog.h> /* * CommonCrypto provides a more stable API than OpenSSL guarantees; * the #define causes it to use the same API for MD5 and SHA1, so the rest of * the code need not change. */ #define COMMON_DIGEST_FOR_OPENSSL #include <CommonCrypto/CommonDigest.h> #include <libkern/OSByteOrder.h> #include <CoreFoundation/CFString.h> #include <System/uuid/uuid.h> #include <System/uuid/namespace.h> #define READ_DEFAULT_ENCODING 1 #ifndef FSUC_ADOPT #define FSUC_ADOPT 'a' #endif #ifndef FSUC_DISOWN #define FSUC_DISOWN 'd' #endif #ifndef FSUC_GETUUID #define FSUC_GETUUID 'k' #endif #ifndef FSUC_SETUUID #define FSUC_SETUUID 's' #endif #ifndef FSUC_MKJNL #define FSUC_MKJNL 'J' #endif #ifndef FSUC_UNJNL #define FSUC_UNJNL 'U' #endif #ifndef FSUC_UNJNL_RAW #define FSUC_UNJNL_RAW 'N' #endif #ifndef FSUC_JNLINFS_RAW #define FSUC_JNLINFS_RAW 'e' #endif #ifndef FSUC_EXTJNL_RAW #define FSUC_EXTJNL_RAW 'E' #endif #ifndef FSUC_JNLINFO #define FSUC_JNLINFO 'I' #endif /* **************************************** L O C A L S ******************************************* */ #define HFS_BLOCK_SIZE 512 char gHFS_FS_NAME[] = "hfs"; char gHFS_FS_NAME_NAME[] = "HFS"; char gNewlineString[] = "\n"; char gMountCommand[] = "/sbin/mount"; char gUnmountCommand[] = "/sbin/umount"; char gReadOnlyOption[] = "-r"; char gReadWriteOption[] = "-w"; char gSuidOption[] = "suid"; char gNoSuidOption[] = "nosuid"; char gDevOption[] = "dev"; char gNoDevOption[] = "nodev"; char gUsePermissionsOption[] = "perm"; char gIgnorePermissionsOption[] = "noperm"; boolean_t gIsEjectable = 0; int gJournalSize = 0; #define AUTO_ADOPT_FIXED 1 #define AUTO_ENTER_FIXED 0 struct FinderAttrBuf { u_int32_t info_length; u_int32_t finderinfo[8]; }; #define VOLUMEUUIDVALUESIZE 2 typedef union VolumeUUID { u_int32_t value[VOLUMEUUIDVALUESIZE]; struct { u_int32_t high; u_int32_t low; } v; } VolumeUUID; #define VOLUMEUUIDLENGTH 16 typedef char VolumeUUIDString[VOLUMEUUIDLENGTH+1]; #define VOLUME_RECORDED 0x80000000 #define VOLUME_USEPERMISSIONS 0x00000001 #define VOLUME_VALIDSTATUSBITS ( VOLUME_USEPERMISSIONS ) typedef void *VolumeStatusDBHandle; void GenerateVolumeUUID(VolumeUUID *newVolumeID); void ConvertVolumeUUIDStringToUUID(const char *UUIDString, VolumeUUID *volumeID); void ConvertVolumeUUIDToString(VolumeUUID *volumeID, char *UUIDString); int OpenVolumeStatusDB(VolumeStatusDBHandle *DBHandlePtr); int GetVolumeStatusDBEntry(VolumeStatusDBHandle DBHandle, VolumeUUID *volumeID, unsigned long *VolumeStatus); int SetVolumeStatusDBEntry(VolumeStatusDBHandle DBHandle, VolumeUUID *volumeID, unsigned long VolumeStatus); int DeleteVolumeStatusDBEntry(VolumeStatusDBHandle DBHandle, VolumeUUID *volumeID); int CloseVolumeStatusDB(VolumeStatusDBHandle DBHandle); /* ************************************ P R O T O T Y P E S *************************************** */ static void DoDisplayUsage( const char * argv[] ); static int DoMount( char * theDeviceNamePtr, const char *rawName, const char * theMountPointPtr, boolean_t isLocked, boolean_t isSetuid, boolean_t isDev ); static int DoProbe( char * rawDeviceNamePtr, char * blockDeviceNamePtr ); static int DoUnmount( const char * theMountPointPtr ); static int DoGetUUIDKey( const char * theDeviceNamePtr, const char *rawName ); static int DoChangeUUIDKey( const char * theDeviceNamePtr ); static int DoAdopt( const char * theDeviceNamePtr, const char *rawName); static int DoDisown( const char * theDeviceNamePtr, const char *rawName); extern int DoMakeJournaled( const char * volNamePtr, int journalSize ); // XXXdbg extern int DoUnJournal( const char * volNamePtr ); // XXXdbg extern int DoGetJournalInfo( const char * volNamePtr ); extern int RawDisableJournaling( const char *devname ); extern int SetJournalInFSState( const char *devname, int journal_in_fs); static int ParseArgs( int argc, const char * argv[], const char ** actionPtr, const char ** mountPointPtr, boolean_t * isEjectablePtr, boolean_t * isLockedPtr, boolean_t * isSetuidPtr, boolean_t * isDevPtr ); static int GetHFSMountPoint(const char *deviceNamePtr, char **pathPtr); static int ReadHeaderBlock(int fd, void *bufptr, off_t *startOffset, VolumeUUID **finderInfoUUIDPtr); static int GetVolumeUUIDRaw(const char *deviceNamePtr, const char *rawName, VolumeUUID *volumeUUIDPtr); static int GetVolumeUUIDAttr(const char *path, VolumeUUID *volumeUUIDPtr); static int GetVolumeUUID(const char *deviceNamePtr, const char *rawName, VolumeUUID *volumeUUIDPtr, boolean_t generate); static int SetVolumeUUIDRaw(const char *deviceNamePtr, VolumeUUID *volumeUUIDPtr); static int SetVolumeUUIDAttr(const char *path, VolumeUUID *volumeUUIDPtr); static int SetVolumeUUID(const char *deviceNamePtr, VolumeUUID *volumeUUIDPtr); static int GetEmbeddedHFSPlusVol(HFSMasterDirectoryBlock * hfsMasterDirectoryBlockPtr, off_t * startOffsetPtr); static int GetNameFromHFSPlusVolumeStartingAt(int fd, off_t hfsPlusVolumeOffset, unsigned char * name_o); static int GetBTreeNodeInfo(int fd, off_t hfsPlusVolumeOffset, u_int32_t blockSize, u_int32_t extentCount, const HFSPlusExtentDescriptor *extentList, u_int32_t *nodeSize, u_int32_t *firstLeafNode); static int GetCatalogOverflowExtents(int fd, off_t hfsPlusVolumeOffset, HFSPlusVolumeHeader *volHdrPtr, HFSPlusExtentDescriptor **catalogExtents, u_int32_t *catalogExtCount); static int LogicalToPhysical(off_t logicalOffset, ssize_t length, u_int32_t blockSize, u_int32_t extentCount, const HFSPlusExtentDescriptor *extentList, off_t *physicalOffset, ssize_t *availableBytes); static int ReadFile(int fd, void *buffer, off_t offset, ssize_t length, off_t volOffset, u_int32_t blockSize, u_int32_t extentCount, const HFSPlusExtentDescriptor *extentList); static ssize_t readAt( int fd, void * buf, off_t offset, ssize_t length ); static ssize_t writeAt( int fd, void * buf, off_t offset, ssize_t length ); static int GetEncodingBias(void); CF_EXPORT Boolean _CFStringGetFileSystemRepresentation(CFStringRef string, UInt8 *buffer, CFIndex maxBufLen); static void uuid_create_md5_from_name(uuid_t result_uuid, const uuid_t namespace, const void *name, int namelen); /* * The fuction CFStringGetSystemEncoding does not work correctly in * our context (autodiskmount deamon). We include a local copy here * so that we can derive the default encoding. Radar 2516316. */ #if READ_DEFAULT_ENCODING #define __kCFUserEncodingFileName ("/.CFUserTextEncoding") static unsigned int __CFStringGetDefaultEncodingForHFSUtil() { struct passwd *passwdp; if ((passwdp = getpwuid(0))) { // root account char buffer[MAXPATHLEN + 1]; int fd; strlcpy(buffer, passwdp->pw_dir, sizeof(buffer)); strlcat(buffer, __kCFUserEncodingFileName, sizeof(buffer)); if ((fd = open(buffer, O_RDONLY, 0)) > 0) { size_t readSize; readSize = read(fd, buffer, MAXPATHLEN); buffer[(readSize < 0 ? 0 : readSize)] = '\0'; close(fd); return strtol(buffer, NULL, 0); } } return 0; // Fallback to smRoman } #endif #define MXENCDNAMELEN 16 /* Maximun length of encoding name string */ struct hfs_mnt_encoding { char encoding_name[MXENCDNAMELEN]; /* encoding type name */ CFStringEncoding encoding_id; /* encoding type number */ }; static struct hfs_mnt_encoding hfs_mnt_encodinglist[] = { { "Arabic", 4 }, { "Armenian", 24 }, { "Bengali", 13 }, { "Burmese", 19 }, { "Celtic", 39 }, { "CentralEurRoman", 29 }, { "ChineseSimp", 25 }, { "ChineseTrad", 2 }, { "Croatian", 36 }, { "Cyrillic", 7 }, { "Devanagari", 9 }, { "Ethiopic", 28 }, { "Farsi", 140 }, { "Gaelic", 40 }, { "Georgian", 23 }, { "Greek", 6 }, { "Gujarati", 11 }, { "Gurmukhi", 10 }, { "Hebrew", 5 }, { "Icelandic", 37 }, { "Japanese", 1 }, { "Kannada", 16 }, { "Khmer", 20 }, { "Korean", 3 }, { "Laotian", 22 }, { "Malayalam", 17 }, { "Mongolian", 27 }, { "Oriya", 12 }, { "Roman", 0 }, /* default */ { "Romanian", 38 }, { "Sinhalese", 18 }, { "Tamil", 14 }, { "Telugu", 15 }, { "Thai", 21 }, { "Tibetan", 26 }, { "Turkish", 35 }, { "Ukrainian", 152 }, { "Vietnamese", 30 }, }; #define KEXT_LOAD_COMMAND "/sbin/kextload" #define ENCODING_MODULE_PATH "/System/Library/Filesystems/hfs.fs/Encodings/" static int load_encoding(CFStringEncoding encoding) { int i; int numEncodings; int pid; char *encodingName; struct stat sb; union wait status; char kmodfile[MAXPATHLEN]; /* Find the encoding that matches the one passed in */ numEncodings = sizeof(hfs_mnt_encodinglist) / sizeof(struct hfs_mnt_encoding); encodingName = NULL; for (i=0; i<numEncodings; ++i) { if (hfs_mnt_encodinglist[i].encoding_id == encoding) { encodingName = hfs_mnt_encodinglist[i].encoding_name; break; } } if (encodingName == NULL) { /* Couldn't figure out which encoding KEXT to load */ syslog(LOG_ERR, "Couldn't find name for encoding #%d", encoding); return FSUR_LOADERR; } snprintf(kmodfile, sizeof(kmodfile), "%sHFS_Mac%s.kext", ENCODING_MODULE_PATH, encodingName); if (stat(kmodfile, &sb) == -1) { /* We recognized the encoding, but couldn't find the KEXT */ syslog(LOG_ERR, "Couldn't stat HFS_Mac%s.kext: %s", encodingName, strerror(errno)); return FSUR_LOADERR; } pid = fork(); if (pid == 0) { (void) execl(KEXT_LOAD_COMMAND, KEXT_LOAD_COMMAND, "-q", kmodfile, NULL); exit(1); /* We can only get here if the exec failed */ } else if (pid != -1) { if ((waitpid(pid, (int *)&status, 0) == pid) && WIFEXITED(status)) { if (WEXITSTATUS(status) != 0) { /* kextload returned an error. Too bad its output doesn't get logged. */ syslog(LOG_ERR, "Couldn't load HFS_Mac%s.kext", encodingName); return FSUR_LOADERR; } } } return FSUR_IO_SUCCESS; } /* ******************************************** main ************************************************ Purpose - This our main entry point to this utility. We get called by the WorkSpace. See ParseArgs for detail info on input arguments. Input - argc - the number of arguments in argv. argv - array of arguments. Output - returns FSUR_IO_SUCCESS if OK else one of the other FSUR_xyz errors in loadable_fs.h. *************************************************************************************************** */ int main (int argc, const char *argv[]) { const char * actionPtr = NULL; char rawDeviceName[MAXPATHLEN]; char blockDeviceName[MAXPATHLEN]; const char * mountPointPtr = NULL; int result = FSUR_IO_SUCCESS; boolean_t isLocked = 0; /* reasonable assumptions */ boolean_t isSetuid = 0; /* reasonable assumptions */ boolean_t isDev = 0; /* reasonable assumptions */ openlog("hfs.util", LOG_PID, LOG_DAEMON); /* Verify our arguments */ if ( (result = ParseArgs( argc, argv, & actionPtr, & mountPointPtr, & gIsEjectable, & isLocked, &isSetuid, &isDev )) != 0 ) { goto AllDone; } /* -- Build our device name (full path), should end up with something like: -- "/dev/disk0s2" */ snprintf(rawDeviceName, sizeof(rawDeviceName), "/dev/r%s", argv[2]); snprintf(blockDeviceName, sizeof(blockDeviceName), "/dev/%s", argv[2]); /* call the appropriate routine to handle the given action argument after becoming root */ switch( * actionPtr ) { case FSUC_PROBE: result = DoProbe(rawDeviceName, blockDeviceName); break; case FSUC_MOUNT: case FSUC_MOUNT_FORCE: result = DoMount(blockDeviceName, rawDeviceName, mountPointPtr, isLocked, isSetuid, isDev); break; case FSUC_UNMOUNT: result = DoUnmount( mountPointPtr ); break; case FSUC_GETUUID: result = DoGetUUIDKey( blockDeviceName, rawDeviceName); break; case FSUC_SETUUID: result = DoChangeUUIDKey( blockDeviceName ); break; case FSUC_ADOPT: result = DoAdopt( blockDeviceName, rawDeviceName); break; case FSUC_DISOWN: result = DoDisown( blockDeviceName, rawDeviceName ); break; case FSUC_MKJNL: if (gJournalSize) { result = DoMakeJournaled( argv[3], gJournalSize ); } else { result = DoMakeJournaled( argv[2], gJournalSize ); } break; case FSUC_UNJNL: result = DoUnJournal( argv[2] ); break; case FSUC_UNJNL_RAW: result = RawDisableJournaling( argv[2] ); break; case FSUC_JNLINFS_RAW: // argv[2] has the device for the external journal. however // we don't need it so we ignore it and just pass argv[3] // which is the hfs volume whose state we're going to change // result = SetJournalInFSState( argv[3], 1 ); break; case FSUC_EXTJNL_RAW: // see the comment for FSUC_JNLINFS_RAW result = SetJournalInFSState( argv[3], 0 ); break; case FSUC_JNLINFO: result = DoGetJournalInfo( argv[2] ); break; default: /* should never get here since ParseArgs should handle this situation */ DoDisplayUsage( argv ); result = FSUR_INVAL; break; } AllDone: exit(result); return result; /*...and make main fit the ANSI spec. */ } /* ***************************** DoMount ******************************** Purpose - This routine will fire off a system command to mount the given device at the given mountpoint. autodiskmount will make sure the mountpoint exists and will remove it at Unmount time. Input - deviceNamePtr - pointer to the device name (full path, like /dev/disk0s2). mountPointPtr - pointer to the mount point. isLocked - a flag Output - returns FSUR_IO_SUCCESS everything is cool else one of several other FSUR_xyz error codes. *********************************************************************** */ static int DoMount(char *deviceNamePtr, const char *rawName, const char *mountPointPtr, boolean_t isLocked, boolean_t isSetuid, boolean_t isDev) { int pid; char *isLockedstr; char *isSetuidstr; char *isDevstr; char *permissionsOption; int result = FSUR_IO_FAIL; union wait status; char encodeopt[16] = ""; CFStringEncoding encoding; VolumeUUID targetVolumeUUID; VolumeStatusDBHandle vsdbhandle = NULL; unsigned long targetVolumeStatus; if (mountPointPtr == NULL || *mountPointPtr == '\0') return (FSUR_IO_FAIL); /* get the volume UUID to check if permissions should be used: */ targetVolumeStatus = 0; if (((result = GetVolumeUUID(deviceNamePtr, rawName, &targetVolumeUUID, FALSE)) != FSUR_IO_SUCCESS) || (targetVolumeUUID.v.high ==0) || (targetVolumeUUID.v.low == 0)) { #if TRACE_HFS_UTIL fprintf(stderr, "hfs.util: DoMount: GetVolumeUUID returned %d.\n", result); #endif #if AUTO_ADOPT_FIXED if (gIsEjectable == 0) { result = DoAdopt( deviceNamePtr, rawName); #if TRACE_HFS_UTIL fprintf(stderr, "hfs.util: DoMount: Auto-adopting %s; result = %d.\n", deviceNamePtr, result); #endif targetVolumeStatus = VOLUME_USEPERMISSIONS; } else { #if TRACE_HFS_UTIL fprintf(stderr, "hfs.util: DoMount: Not adopting ejectable %s.\n", deviceNamePtr); #endif targetVolumeStatus = 0; } #endif } else { /* We've got a real volume UUID! */ #if TRACE_HFS_UTIL fprintf(stderr, "hfs.util: DoMount: UUID = %08lX%08lX.\n", targetVolumeUUID.v.high, targetVolumeUUID.v.low); #endif if ((result = OpenVolumeStatusDB(&vsdbhandle)) != 0) { /* Can't even get access to the volume info db; assume permissions are OK. */ #if TRACE_HFS_UTIL fprintf(stderr, "hfs.util: DoMount: OpenVolumeStatusDB returned %d; ignoring permissions.\n", result); #endif targetVolumeStatus = VOLUME_USEPERMISSIONS; } else { #if TRACE_HFS_UTIL fprintf(stderr, "hfs.util: DoMount: Looking up volume status...\n"); #endif if ((result = GetVolumeStatusDBEntry(vsdbhandle, &targetVolumeUUID, &targetVolumeStatus)) != 0) { #if TRACE_HFS_UTIL fprintf(stderr, "hfs.util: DoMount: GetVolumeStatusDBEntry returned %d.\n", result); #endif #if AUTO_ENTER_FIXED if (gIsEjectable == 0) { result = DoAdopt( deviceNamePtr, rawName ); #if TRACE_HFS_UTIL fprintf(stderr, "hfs.util: DoMount: Auto-adopting %s; result = %d.\n", deviceNamePtr, result); #endif targetVolumeStatus = VOLUME_USEPERMISSIONS; } else { #if TRACE_HFS_UTIL fprintf(stderr, "hfs.util: DoMount: Not adopting ejectable %s.\n", deviceNamePtr); #endif targetVolumeStatus = 0; } #else targetVolumeStatus = 0; #endif } (void)CloseVolumeStatusDB(vsdbhandle); vsdbhandle = NULL; } } pid = fork(); if (pid == 0) { isLockedstr = isLocked ? gReadOnlyOption : gReadWriteOption; isSetuidstr = isSetuid ? gSuidOption : gNoSuidOption; isDevstr = isDev ? gDevOption : gNoDevOption; permissionsOption = (targetVolumeStatus & VOLUME_USEPERMISSIONS) ? gUsePermissionsOption : gIgnorePermissionsOption; /* get default encoding value (for hfs volumes) */ #if READ_DEFAULT_ENCODING encoding = __CFStringGetDefaultEncodingForHFSUtil(); #else encoding = CFStringGetSystemEncoding(); #endif snprintf(encodeopt, sizeof(encodeopt), "-e=%d", (int)encoding); #if TRACE_HFS_UTIL fprintf(stderr, "hfs.util: %s %s -o -x -o %s -o %s -o -u=unknown,-g=unknown,-m=0777 -t %s %s %s ...\n", gMountCommand, isLockedstr, encodeopt, permissionsOption, gHFS_FS_NAME, deviceNamePtr, mountPointPtr); #endif (void) execl(gMountCommand, gMountCommand, isLockedstr, "-o", isSetuidstr, "-o", isDevstr, "-o", encodeopt, "-o", permissionsOption, "-o", "-u=unknown,-g=unknown,-m=0777", "-t", gHFS_FS_NAME, deviceNamePtr, mountPointPtr, NULL); /* IF WE ARE HERE, WE WERE UNSUCCESFULL */ return (FSUR_IO_FAIL); } if (pid == -1) return (FSUR_IO_FAIL); /* Success! */ if ((wait4(pid, (int *)&status, 0, NULL) == pid) && (WIFEXITED(status))) result = status.w_retcode; else result = -1; return (result == 0) ? FSUR_IO_SUCCESS : FSUR_IO_FAIL; } /* ****************************************** DoUnmount ********************************************* Purpose - This routine will fire off a system command to unmount the given device. Input - theDeviceNamePtr - pointer to the device name (full path, like /dev/disk0s2). Output - returns FSUR_IO_SUCCESS everything is cool else FSUR_IO_FAIL. *************************************************************************************************** */ static int DoUnmount(const char * theMountPointPtr) { int pid; union wait status; int result; if (theMountPointPtr == NULL || *theMountPointPtr == '\0') return (FSUR_IO_FAIL); pid = fork(); if (pid == 0) { #if TRACE_HFS_UTIL fprintf(stderr, "hfs.util: %s %s ...\n", gUnmountCommand, theMountPointPtr); #endif (void) execl(gUnmountCommand, gUnmountCommand, theMountPointPtr, NULL); /* IF WE ARE HERE, WE WERE UNSUCCESFULL */ return (FSUR_IO_FAIL); } if (pid == -1) return (FSUR_IO_FAIL); /* Success! */ if ((wait4(pid, (int *)&status, 0, NULL) == pid) && (WIFEXITED(status))) result = status.w_retcode; else result = -1; return (result == 0 ? FSUR_IO_SUCCESS : FSUR_IO_FAIL); } /* DoUnmount */ /* PrintVolumeNameAttr Get the volume name of the volume mounted at "path". Print that volume name to standard out. Returns: FSUR_RECOGNIZED, FSUR_IO_FAIL */ struct VolumeNameBuf { u_int32_t info_length; attrreference_t name_ref; char buffer[1024]; }; static int PrintVolumeNameAttr(const char *path) { struct attrlist alist; struct VolumeNameBuf volNameInfo; int result; /* Set up the attrlist structure to get the volume's Finder Info */ alist.bitmapcount = 5; alist.reserved = 0; alist.commonattr = 0; alist.volattr = ATTR_VOL_INFO | ATTR_VOL_NAME; alist.dirattr = 0; alist.fileattr = 0; alist.forkattr = 0; /* Get the Finder Info */ result = getattrlist(path, &alist, &volNameInfo, sizeof(volNameInfo), 0); if (result) { result = FSUR_IO_FAIL; goto Err_Exit; } /* Print the name to standard out */ printf("%.*s", (int) volNameInfo.name_ref.attr_length, ((char *) &volNameInfo.name_ref) + volNameInfo.name_ref.attr_dataoffset); result = FSUR_RECOGNIZED; Err_Exit: return result; } /* ******************************************* DoProbe ********************************************** Purpose - This routine will open the given device and check to make sure there is media that looks like an HFS. If it is HFS, then print the volume name to standard output. Input - rawDeviceNamePtr - pointer to the full path of the raw device (like /dev/rdisk0s2). blockDeviceNamePtr - pointer to the full path of the non-raw device (like /dev/disk0s2). Output - returns FSUR_RECOGNIZED if we can handle the media else one of the FSUR_xyz error codes. *************************************************************************************************** */ static int DoProbe(char *rawDeviceNamePtr, char *blockDeviceNamePtr) { int result = FSUR_UNRECOGNIZED; int fd = 0; char * bufPtr; HFSMasterDirectoryBlock * mdbPtr; HFSPlusVolumeHeader * volHdrPtr; u_char volnameUTF8[NAME_MAX+1]; /* * Determine if there is a volume already mounted from this device. If * there is, and it is HFS, then we need to get the volume name via * getattrlist. * * NOTE: We're using bufPtr to hold a pointer to a path. */ bufPtr = NULL; result = GetHFSMountPoint(blockDeviceNamePtr, &bufPtr); if (result != FSUR_IO_SUCCESS) { goto Err_Exit; } if (bufPtr != NULL) { /* There is an HFS volume mounted from the device. */ result = PrintVolumeNameAttr(bufPtr); goto Err_Exit; } /* * If we get here, there is no volume mounted from this device, so * go probe the raw device directly. */ bufPtr = (char *)malloc(HFS_BLOCK_SIZE); if ( ! bufPtr ) { result = FSUR_UNRECOGNIZED; goto Return; } mdbPtr = (HFSMasterDirectoryBlock *) bufPtr; volHdrPtr = (HFSPlusVolumeHeader *) bufPtr; fd = open( rawDeviceNamePtr, O_RDONLY, 0 ); if( fd <= 0 ) { result = FSUR_IO_FAIL; goto Return; } /* * Read the HFS Master Directory Block from sector 2 */ result = readAt(fd, bufPtr, (off_t)(2 * HFS_BLOCK_SIZE), HFS_BLOCK_SIZE); if (FSUR_IO_FAIL == result) goto Return; /* get classic HFS volume name (from MDB) */ if (OSSwapBigToHostInt16(mdbPtr->drSigWord) == kHFSSigWord && OSSwapBigToHostInt16(mdbPtr->drEmbedSigWord) != kHFSPlusSigWord) { Boolean cfOK; CFStringRef cfstr; CFStringEncoding encoding; /* Some poorly mastered HFS CDs have an empty MDB name field! */ if (mdbPtr->drVN[0] == '\0') { strcpy((char *)&mdbPtr->drVN[1], gHFS_FS_NAME_NAME); mdbPtr->drVN[0] = strlen(gHFS_FS_NAME_NAME); } /* Check for an encoding hint in the Finder Info (field 4). */ encoding = GET_HFS_TEXT_ENCODING(OSSwapBigToHostInt32(mdbPtr->drFndrInfo[4])); if (encoding == kCFStringEncodingInvalidId) { /* Next try the encoding bias in the kernel. */ encoding = GetEncodingBias(); if (encoding == 0 || encoding == kCFStringEncodingInvalidId) encoding = __CFStringGetDefaultEncodingForHFSUtil(); } cfstr = CFStringCreateWithPascalString(kCFAllocatorDefault, mdbPtr->drVN, encoding); if (cfstr == NULL) { result = FSUR_INVAL; goto Return; } cfOK = _CFStringGetFileSystemRepresentation(cfstr, volnameUTF8, NAME_MAX); CFRelease(cfstr); if (!cfOK && encoding != kCFStringEncodingMacRoman) { /* default to MacRoman on conversion errors */ cfstr = CFStringCreateWithPascalString(kCFAllocatorDefault, mdbPtr->drVN, kCFStringEncodingMacRoman); _CFStringGetFileSystemRepresentation(cfstr, volnameUTF8, NAME_MAX); CFRelease(cfstr); encoding = kCFStringEncodingMacRoman; } /* Preload the encoding converter so mount_hfs can run as an ordinary user. */ if (encoding != kCFStringEncodingMacRoman) { if (load_encoding(encoding) != FSUR_IO_SUCCESS) { encoding = kCFStringEncodingMacRoman; cfstr = CFStringCreateWithPascalString(kCFAllocatorDefault, mdbPtr->drVN, encoding); _CFStringGetFileSystemRepresentation(cfstr, volnameUTF8, NAME_MAX); CFRelease(cfstr); } } /* get HFS Plus volume name (from Catalog) */ } else if ((OSSwapBigToHostInt16(volHdrPtr->signature) == kHFSPlusSigWord) || (OSSwapBigToHostInt16(volHdrPtr->signature) == kHFSXSigWord) || (OSSwapBigToHostInt16(mdbPtr->drSigWord) == kHFSSigWord && OSSwapBigToHostInt16(mdbPtr->drEmbedSigWord) == kHFSPlusSigWord)) { off_t startOffset; if (OSSwapBigToHostInt16(volHdrPtr->signature) == kHFSSigWord) { /* embedded volume, first find offset */ result = GetEmbeddedHFSPlusVol(mdbPtr, &startOffset); if ( result != FSUR_IO_SUCCESS ) goto Return; } else { startOffset = 0; } result = GetNameFromHFSPlusVolumeStartingAt(fd, startOffset, volnameUTF8); } else { result = FSUR_UNRECOGNIZED; } if (FSUR_IO_SUCCESS == result) { /* Print the volume name to standard output */ write(1, volnameUTF8, strlen((char *)volnameUTF8)); result = FSUR_RECOGNIZED; } Return: if ( bufPtr ) free( bufPtr ); if (fd > 0) close(fd); Err_Exit: return result; } /* DoProbe */ /* * Create a version 3 UUID from a unique "name" in the given "name space". * Version 3 UUID are derived using "name" via MD5 checksum. * * Parameters: * result_uuid - resulting UUID. * namespace - namespace in which given name exists and UUID should be created. * name - unique string used to create version 3 UUID. * namelen - length of the name string. */ static void uuid_create_md5_from_name(uuid_t result_uuid, const uuid_t namespace, const void *name, int namelen) { MD5_CTX c; MD5_Init(&c); MD5_Update(&c, namespace, sizeof(uuid_t)); MD5_Update(&c, name, namelen); MD5_Final(result_uuid, &c); result_uuid[6] = (result_uuid[6] & 0x0F) | 0x30; result_uuid[8] = (result_uuid[8] & 0x3F) | 0x80; } /* **************************************** DoGetUUIDKey ******************************************* Purpose - This routine will open the given block device and return the 128-bit volume UUID in text form written to stdout. Input - theDeviceNamePtr - pointer to the device name (full path, like /dev/disk0s2). Output - returns FSUR_IO_SUCCESS or else one of the FSUR_xyz error codes. *************************************************************************************************** */ static int DoGetUUIDKey( const char * theDeviceNamePtr, const char *rawName) { int result; VolumeUUID targetVolumeUUID; uuid_t uuid; char uuidLine[40]; unsigned char rawUUID[8]; if ((result = GetVolumeUUID(theDeviceNamePtr, rawName, &targetVolumeUUID, FALSE)) != FSUR_IO_SUCCESS) goto Err_Exit; ((uint32_t *)rawUUID)[0] = OSSwapHostToBigInt32(targetVolumeUUID.v.high); ((uint32_t *)rawUUID)[1] = OSSwapHostToBigInt32(targetVolumeUUID.v.low); uuid_create_md5_from_name(uuid, kFSUUIDNamespaceSHA1, rawUUID, sizeof(rawUUID)); uuid_unparse(uuid, uuidLine); write(1, uuidLine, strlen(uuidLine)); result = FSUR_IO_SUCCESS; Err_Exit: return result; } /* *************************************** DoChangeUUIDKey ****************************************** Purpose - This routine will change the UUID on the specified block device. Input - theDeviceNamePtr - pointer to the device name (full path, like /dev/disk0s2). Output - returns FSUR_IO_SUCCESS or else one of the FSUR_xyz error codes. *************************************************************************************************** */ static int DoChangeUUIDKey( const char * theDeviceNamePtr ) { int result; VolumeUUID newVolumeUUID; GenerateVolumeUUID(&newVolumeUUID); result = SetVolumeUUID(theDeviceNamePtr, &newVolumeUUID); return result; } /* **************************************** DoAdopt ******************************************* Purpose - This routine will add the UUID of the specified block device to the list of local volumes. Input - theDeviceNamePtr - pointer to the device name (full path, like /dev/disk0s2). Output - returns FSUR_IO_SUCCESS or else one of the FSUR_xyz error codes. *************************************************************************************************** */ static int DoAdopt( const char * theDeviceNamePtr, const char *rawName) { int result, closeresult; VolumeUUID targetVolumeUUID; VolumeStatusDBHandle vsdbhandle = NULL; unsigned long targetVolumeStatus; if ((result = GetVolumeUUID(theDeviceNamePtr, rawName, &targetVolumeUUID, TRUE)) != FSUR_IO_SUCCESS) goto Err_Return; if ((result = OpenVolumeStatusDB(&vsdbhandle)) != 0) goto Err_Exit; if ((result = GetVolumeStatusDBEntry(vsdbhandle, &targetVolumeUUID, &targetVolumeStatus)) != 0) { targetVolumeStatus = 0; } targetVolumeStatus = (targetVolumeStatus & VOLUME_VALIDSTATUSBITS) | VOLUME_USEPERMISSIONS; if ((result = SetVolumeStatusDBEntry(vsdbhandle, &targetVolumeUUID, targetVolumeStatus)) != 0) goto Err_Exit; result = FSUR_IO_SUCCESS; Err_Exit: if (vsdbhandle) { closeresult = CloseVolumeStatusDB(vsdbhandle); vsdbhandle = NULL; if (result == FSUR_IO_SUCCESS) result = closeresult; } if ((result != 0) && (result != FSUR_IO_SUCCESS)) result = FSUR_IO_FAIL; Err_Return: #if TRACE_HFS_UTIL if (result != FSUR_IO_SUCCESS) fprintf(stderr, "DoAdopt: returning %d...\n", result); #endif return result; } /* **************************************** DoDisown ******************************************* Purpose - This routine will change the status of the specified block device to ignore its permissions. Input - theDeviceNamePtr - pointer to the device name (full path, like /dev/disk0s2). Output - returns FSUR_IO_SUCCESS or else one of the FSUR_xyz error codes. *************************************************************************************************** */ static int DoDisown( const char * theDeviceNamePtr, const char *rawName) { int result, closeresult; VolumeUUID targetVolumeUUID; VolumeStatusDBHandle vsdbhandle = NULL; unsigned long targetVolumeStatus; if ((result = GetVolumeUUID(theDeviceNamePtr, rawName, &targetVolumeUUID, TRUE)) != FSUR_IO_SUCCESS) goto Err_Return; if ((result = OpenVolumeStatusDB(&vsdbhandle)) != 0) goto Err_Exit; if ((result = GetVolumeStatusDBEntry(vsdbhandle, &targetVolumeUUID, &targetVolumeStatus)) != 0) { targetVolumeStatus = 0; } targetVolumeStatus = (targetVolumeStatus & VOLUME_VALIDSTATUSBITS) & ~VOLUME_USEPERMISSIONS; if ((result = SetVolumeStatusDBEntry(vsdbhandle, &targetVolumeUUID, targetVolumeStatus)) != 0) goto Err_Exit; result = FSUR_IO_SUCCESS; Err_Exit: if (vsdbhandle) { closeresult = CloseVolumeStatusDB(vsdbhandle); vsdbhandle = NULL; if (result == FSUR_IO_SUCCESS) result = closeresult; } if ((result != 0) && (result != FSUR_IO_SUCCESS)) { #if TRACE_HFS_UTIL if (result != 0) fprintf(stderr, "DoDisown: result = %d; changing to %d...\n", result, FSUR_IO_FAIL); #endif result = FSUR_IO_FAIL; } Err_Return: #if TRACE_HFS_UTIL if (result != FSUR_IO_SUCCESS) fprintf(stderr, "DoDisown: returning %d...\n", result); #endif return result; } static int get_multiplier(char c) { if (tolower(c) == 'k') { return 1024; } else if (tolower(c) == 'm') { return 1024 * 1024; } else if (tolower(c) == 'g') { return 1024 * 1024 * 1024; } return 1; } /* **************************************** ParseArgs ******************************************** Purpose - This routine will make sure the arguments passed in to us are cool. Here is how this utility is used: usage: hfs.util actionArg deviceArg [mountPointArg] [flagsArg] actionArg: -p (Probe for mounting) -P (Probe for initializing - not supported) -m (Mount) -r (Repair - not supported) -u (Unmount) -M (Force Mount) -i (Initialize - not supported) deviceArg: disk0s2 (for example) mountPointArg: /foo/bar/ (required for Mount and Force Mount actions) flagsArg: (these are ignored for CDROMs) either "readonly" OR "writable" either "removable" OR "fixed" either "nosuid" or "suid" either "nodev" or "dev" examples: hfs.util -p disk0s2 removable writable hfs.util -p disk0s2 removable readonly hfs.util -m disk0s2 /my/hfs Input - argc - the number of arguments in argv. argv - array of arguments. Output - returns FSUR_INVAL if we find a bad argument else 0. *************************************************************************************************** */ static int ParseArgs(int argc, const char *argv[], const char ** actionPtr, const char ** mountPointPtr, boolean_t * isEjectablePtr, boolean_t * isLockedPtr, boolean_t * isSetuidPtr, boolean_t * isDevPtr) { int result = FSUR_INVAL; int deviceLength, doLengthCheck = 1; int index; int mounting = 0; /* Must have at least 3 arguments and the action argument must start with a '-' */ if ( (argc < 3) || (argv[1][0] != '-') ) { DoDisplayUsage( argv ); goto Return; } /* we only support actions Probe, Mount, Force Mount, and Unmount */ * actionPtr = & argv[1][1]; switch ( argv[1][1] ) { case FSUC_PROBE: /* action Probe and requires 5 arguments (need the flags) */ if ( argc < 5 ) { DoDisplayUsage( argv ); goto Return; } else { index = 3; } break; case FSUC_UNMOUNT: /* Note: the device argument in argv[2] is checked further down but ignored. */ * mountPointPtr = argv[3]; index = 0; /* No isEjectable/isLocked flags for unmount. */ break; case FSUC_MOUNT: case FSUC_MOUNT_FORCE: /* action Mount and ForceMount require 8 arguments (need the mountpoint and the flags) */ if ( argc < 8 ) { DoDisplayUsage( argv ); goto Return; } else { * mountPointPtr = argv[3]; index = 4; mounting = 1; } break; case FSUC_GETUUID: index = 0; break; case FSUC_SETUUID: index = 0; break; case FSUC_ADOPT: index = 0; break; case FSUC_DISOWN: index = 0; break; // XXXdbg case FSUC_MKJNL: index = 0; doLengthCheck = 0; if (isdigit(argv[2][0])) { char *ptr; gJournalSize = strtoul(argv[2], &ptr, 0); if (ptr) { gJournalSize *= get_multiplier(*ptr); } return 0; } break; case FSUC_UNJNL: index = 0; doLengthCheck = 0; break; case FSUC_UNJNL_RAW: index = 0; doLengthCheck = 0; break; case FSUC_JNLINFS_RAW: index = 0; doLengthCheck = 0; break; case FSUC_EXTJNL_RAW: index = 0; doLengthCheck = 0; break; case FSUC_JNLINFO: index = 0; doLengthCheck = 0; break; // XXXdbg default: DoDisplayUsage( argv ); goto Return; break; } /* Make sure device (argv[2]) is something reasonable */ deviceLength = strlen( argv[2] ); if ( doLengthCheck && (deviceLength < 3 || deviceLength > NAME_MAX) ) { DoDisplayUsage( argv ); goto Return; } if ( index ) { /* Flags: removable/fixed. */ if ( 0 == strcmp(argv[index],"removable") ) { * isEjectablePtr = 1; } else if ( 0 == strcmp(argv[index],"fixed") ) { * isEjectablePtr = 0; } else { printf("hfs.util: ERROR: unrecognized flag (removable/fixed) argv[%d]='%s'\n",index,argv[index]); } /* Flags: readonly/writable. */ if ( 0 == strcmp(argv[index+1],"readonly") ) { * isLockedPtr = 1; } else if ( 0 == strcmp(argv[index+1],"writable") ) { * isLockedPtr = 0; } else { printf("hfs.util: ERROR: unrecognized flag (readonly/writable) argv[%d]='%s'\n",index,argv[index+1]); } if (mounting) { /* Flags: suid/nosuid. */ if ( 0 == strcmp(argv[index+2],"suid") ) { * isSetuidPtr = 1; } else if ( 0 == strcmp(argv[index+2],"nosuid") ) { * isSetuidPtr = 0; } else { printf("hfs.util: ERROR: unrecognized flag (suid/nosuid) argv[%d]='%s'\n",index,argv[index+2]); } /* Flags: dev/nodev. */ if ( 0 == strcmp(argv[index+3],"dev") ) { * isDevPtr = 1; } else if ( 0 == strcmp(argv[index+3],"nodev") ) { * isDevPtr = 0; } else { printf("hfs.util: ERROR: unrecognized flag (dev/nodev) argv[%d]='%s'\n",index,argv[index+3]); } } } result = 0; Return: return result; } /* ParseArgs */ /* *************************************** DoDisplayUsage ******************************************** Purpose - This routine will do a printf of the correct usage for this utility. Input - argv - array of arguments. Output - NA. *************************************************************************************************** */ static void DoDisplayUsage(const char *argv[]) { printf("usage: %s action_arg device_arg [mount_point_arg] [Flags] \n", argv[0]); printf("action_arg:\n"); printf(" -%c (Probe for mounting)\n", FSUC_PROBE); printf(" -%c (Mount)\n", FSUC_MOUNT); printf(" -%c (Unmount)\n", FSUC_UNMOUNT); printf(" -%c (Force Mount)\n", FSUC_MOUNT_FORCE); #ifdef HFS_UUID_SUPPORT printf(" -%c (Get UUID Key)\n", FSUC_GETUUID); printf(" -%c (Set UUID Key)\n", FSUC_SETUUID); #endif HFS_UUID_SUPPORT printf(" -%c (Adopt permissions)\n", FSUC_ADOPT); printf(" -%c (Make a file system journaled)\n", FSUC_MKJNL); printf(" -%c (Turn off journaling on a file system)\n", FSUC_UNJNL); printf(" -%c (Turn off journaling on a raw device)\n", FSUC_UNJNL_RAW); printf(" -%c (Disable use of an external journal on a raw device)\n", FSUC_JNLINFS_RAW); printf(" -%c (Enable the use of an external journal on a raw device)\n", FSUC_EXTJNL_RAW); printf(" -%c (Get size & location of journaling on a file system)\n", FSUC_JNLINFO); printf("device_arg:\n"); printf(" device we are acting upon (for example, 'disk0s2')\n"); printf(" if '-%c' or '-%c' is specified, this should be the\n", FSUC_MKJNL, FSUC_UNJNL); printf(" name of the file system we're to act on (for example, '/Volumes/foo' or '/')\n"); printf("mount_point_arg:\n"); printf(" required for Mount and Force Mount \n"); printf("Flags:\n"); printf(" required for Mount, Force Mount and Probe\n"); printf(" indicates removable or fixed (for example 'fixed')\n"); printf(" indicates readonly or writable (for example 'readonly')\n"); printf(" indicates suid or nosuid (for example 'suid')\n"); printf(" indicates dev or nodev (for example 'dev')\n"); printf("Examples:\n"); printf(" %s -p disk0s2 fixed writable\n", argv[0]); printf(" %s -m disk0s2 /my/hfs removable readonly nosuid nodev\n", argv[0]); return; } /* DoDisplayUsage */ /* GetHFSMountPoint Given a path to a device, determine if a volume is mounted on that device. If there is an HFS volume, return its path and FSUR_IO_SUCCESS. If there is a non-HFS volume, return FSUR_UNRECOGNIZED. If there is no volume mounted on the device, set *pathPtr to NULL and return FSUR_IO_SUCCESS. Returns: FSUR_IO_SUCCESS, FSUR_IO_FAIL, FSUR_UNRECOGNIZED */ static int GetHFSMountPoint(const char *deviceNamePtr, char **pathPtr) { int result; int i, numMounts; struct statfs *buf; /* Assume no mounted volume found */ *pathPtr = NULL; result = FSUR_IO_SUCCESS; numMounts = getmntinfo(&buf, MNT_NOWAIT); if (numMounts == 0) return FSUR_IO_FAIL; for (i=0; i<numMounts; ++i) { if (!strcmp(deviceNamePtr, buf[i].f_mntfromname)) { /* Found a mounted volume; check the type */ if (!strcmp(buf[i].f_fstypename, "hfs")) { *pathPtr = buf[i].f_mntonname; /* result = FSUR_IO_SUCCESS, above */ } else { result = FSUR_UNRECOGNIZED; } break; } } return result; } /* ReadHeaderBlock Read the Master Directory Block or Volume Header Block from an HFS, HFS Plus, or HFSX volume into a caller-supplied buffer. Return the offset of an embedded HFS Plus volume (or 0 if not embedded HFS Plus). Return a pointer to the volume UUID in the Finder Info. Returns: FSUR_IO_SUCCESS, FSUR_IO_FAIL, FSUR_UNRECOGNIZED */ static int ReadHeaderBlock(int fd, void *bufPtr, off_t *startOffset, VolumeUUID **finderInfoUUIDPtr) { int result; HFSMasterDirectoryBlock * mdbPtr; HFSPlusVolumeHeader * volHdrPtr; mdbPtr = bufPtr; volHdrPtr = bufPtr; /* * Read the HFS Master Directory Block or Volume Header from sector 2 */ *startOffset = 0; result = readAt(fd, bufPtr, (off_t)(2 * HFS_BLOCK_SIZE), HFS_BLOCK_SIZE); if (result != FSUR_IO_SUCCESS) goto Err_Exit; /* * If this is a wrapped HFS Plus volume, read the Volume Header from * sector 2 of the embedded volume. */ if (OSSwapBigToHostInt16(mdbPtr->drSigWord) == kHFSSigWord && OSSwapBigToHostInt16(mdbPtr->drEmbedSigWord) == kHFSPlusSigWord) { result = GetEmbeddedHFSPlusVol(mdbPtr, startOffset); if (result != FSUR_IO_SUCCESS) goto Err_Exit; result = readAt(fd, bufPtr, *startOffset + (off_t)(2*HFS_BLOCK_SIZE), HFS_BLOCK_SIZE); if (result != FSUR_IO_SUCCESS) goto Err_Exit; } /* * At this point, we have the MDB for plain HFS, or VHB for HFS Plus and HFSX * volumes (including wrapped HFS Plus). Verify the signature and grab the * UUID from the Finder Info. */ if (OSSwapBigToHostInt16(mdbPtr->drSigWord) == kHFSSigWord) { *finderInfoUUIDPtr = (VolumeUUID *)(&mdbPtr->drFndrInfo[6]); } else if (OSSwapBigToHostInt16(volHdrPtr->signature) == kHFSPlusSigWord || OSSwapBigToHostInt16(volHdrPtr->signature) == kHFSXSigWord) { *finderInfoUUIDPtr = (VolumeUUID *)&volHdrPtr->finderInfo[24]; } else { result = FSUR_UNRECOGNIZED; } Err_Exit: return result; } /* GetVolumeUUIDRaw Read the UUID from an unmounted volume, by doing direct access to the device. Assumes the caller has already determined that a volume is not mounted on the device. Returns: FSUR_IO_SUCCESS, FSUR_IO_FAIL, FSUR_UNRECOGNIZED */ static int GetVolumeUUIDRaw(const char *deviceNamePtr, const char *rawName, VolumeUUID *volumeUUIDPtr) { int fd = 0; char * bufPtr; off_t startOffset; VolumeUUID *finderInfoUUIDPtr; int result; int error; bufPtr = (char *)malloc(HFS_BLOCK_SIZE); if ( ! bufPtr ) { result = FSUR_UNRECOGNIZED; goto Err_Exit; } fd = open( deviceNamePtr, O_RDONLY, 0); if (fd <= 0) { error = errno; #if TRACE_HFS_UTIL fprintf(stderr, "hfs.util: GetVolumeUUIDRaw: device (%s) open failed (errno = %d).\n", deviceNamePtr, errno); #endif if (error == EBUSY) { /* If it was busy, then retry, this time using the raw device */ fd = open (rawName, O_RDONLY, 0); if (fd <= 0) { #if TRACE_HFS_UTIL fprintf(stderr, "hfs.util: GetVolumeUUIDRaw: device (%s) open failed (errno = %d).\n", rawName, errno); #endif result = FSUR_IO_FAIL; goto Err_Exit; } } else { result = FSUR_IO_FAIL; goto Err_Exit; } } /* * Get the pointer to the volume UUID in the Finder Info */ result = ReadHeaderBlock(fd, bufPtr, &startOffset, &finderInfoUUIDPtr); if (result != FSUR_IO_SUCCESS) goto Err_Exit; /* * Copy the volume UUID out of the Finder Info */ volumeUUIDPtr->v.high = OSSwapBigToHostInt32(finderInfoUUIDPtr->v.high); volumeUUIDPtr->v.low = OSSwapBigToHostInt32(finderInfoUUIDPtr->v.low); Err_Exit: if (fd > 0) close(fd); if (bufPtr) free(bufPtr); #if TRACE_HFS_UTIL if (result != FSUR_IO_SUCCESS) fprintf(stderr, "hfs.util: GetVolumeUUIDRaw: result = %d...\n", result); #endif return (result == FSUR_IO_SUCCESS) ? FSUR_IO_SUCCESS : FSUR_IO_FAIL; } /* SetVolumeUUIDRaw Write a previously generated UUID to an unmounted volume, by doing direct access to the device. Assumes the caller has already determined that a volume is not mounted on the device. Returns: FSUR_IO_SUCCESS, FSUR_IO_FAIL, FSUR_UNRECOGNIZED */ static int SetVolumeUUIDRaw(const char *deviceNamePtr, VolumeUUID *volumeUUIDPtr) { int fd = 0; char * bufPtr; off_t startOffset; VolumeUUID *finderInfoUUIDPtr; int result; bufPtr = (char *)malloc(HFS_BLOCK_SIZE); if ( ! bufPtr ) { result = FSUR_UNRECOGNIZED; goto Err_Exit; } fd = open( deviceNamePtr, O_RDWR, 0); if (fd <= 0) { #if TRACE_HFS_UTIL fprintf(stderr, "hfs.util: SetVolumeUUIDRaw: device open failed (errno = %d).\n", errno); #endif result = FSUR_IO_FAIL; goto Err_Exit; } /* * Get the pointer to the volume UUID in the Finder Info */ result = ReadHeaderBlock(fd, bufPtr, &startOffset, &finderInfoUUIDPtr); if (result != FSUR_IO_SUCCESS) goto Err_Exit; /* * Update the UUID in the Finder Info */ finderInfoUUIDPtr->v.high = OSSwapHostToBigInt32(volumeUUIDPtr->v.high); finderInfoUUIDPtr->v.low = OSSwapHostToBigInt32(volumeUUIDPtr->v.low); /* * Write the modified MDB or VHB back to disk */ result = writeAt(fd, bufPtr, startOffset + (off_t)(2*HFS_BLOCK_SIZE), HFS_BLOCK_SIZE); Err_Exit: if (fd > 0) close(fd); if (bufPtr) free(bufPtr); #if TRACE_HFS_UTIL if (result != FSUR_IO_SUCCESS) fprintf(stderr, "hfs.util: SetVolumeUUIDRaw: result = %d...\n", result); #endif return (result == FSUR_IO_SUCCESS) ? FSUR_IO_SUCCESS : FSUR_IO_FAIL; } /* GetVolumeUUIDAttr Read the UUID from a mounted volume, by calling getattrlist(). Assumes the path is the mount point of an HFS volume. Returns: FSUR_IO_SUCCESS, FSUR_IO_FAIL */ static int GetVolumeUUIDAttr(const char *path, VolumeUUID *volumeUUIDPtr) { struct attrlist alist; struct FinderAttrBuf volFinderInfo; VolumeUUID *finderInfoUUIDPtr; int result; /* Set up the attrlist structure to get the volume's Finder Info */ alist.bitmapcount = 5; alist.reserved = 0; alist.commonattr = ATTR_CMN_FNDRINFO; alist.volattr = ATTR_VOL_INFO; alist.dirattr = 0; alist.fileattr = 0; alist.forkattr = 0; /* Get the Finder Info */ result = getattrlist(path, &alist, &volFinderInfo, sizeof(volFinderInfo), 0); if (result) { result = FSUR_IO_FAIL; goto Err_Exit; } /* Copy the UUID from the Finder Into to caller's buffer */ finderInfoUUIDPtr = (VolumeUUID *)(&volFinderInfo.finderinfo[6]); volumeUUIDPtr->v.high = OSSwapBigToHostInt32(finderInfoUUIDPtr->v.high); volumeUUIDPtr->v.low = OSSwapBigToHostInt32(finderInfoUUIDPtr->v.low); result = FSUR_IO_SUCCESS; Err_Exit: return result; } /* SetVolumeUUIDAttr Write a UUID to a mounted volume, by calling setattrlist(). Assumes the path is the mount point of an HFS volume. Returns: FSUR_IO_SUCCESS, FSUR_IO_FAIL */ static int SetVolumeUUIDAttr(const char *path, VolumeUUID *volumeUUIDPtr) { struct attrlist alist; struct FinderAttrBuf volFinderInfo; VolumeUUID *finderInfoUUIDPtr; int result; /* Set up the attrlist structure to get the volume's Finder Info */ alist.bitmapcount = 5; alist.reserved = 0; alist.commonattr = ATTR_CMN_FNDRINFO; alist.volattr = ATTR_VOL_INFO; alist.dirattr = 0; alist.fileattr = 0; alist.forkattr = 0; /* Get the Finder Info */ result = getattrlist(path, &alist, &volFinderInfo, sizeof(volFinderInfo), 0); if (result) { result = FSUR_IO_FAIL; goto Err_Exit; } /* Update the UUID in the Finder Info */ finderInfoUUIDPtr = (VolumeUUID *)(&volFinderInfo.finderinfo[6]); finderInfoUUIDPtr->v.high = OSSwapHostToBigInt32(volumeUUIDPtr->v.high); finderInfoUUIDPtr->v.low = OSSwapHostToBigInt32(volumeUUIDPtr->v.low); /* Write the Finder Info back to the volume */ result = setattrlist(path, &alist, &volFinderInfo.finderinfo, sizeof(volFinderInfo.finderinfo), 0); if (result) { result = FSUR_IO_FAIL; goto Err_Exit; } result = FSUR_IO_SUCCESS; Err_Exit: return result; } /* GetVolumeUUID Return the UUID of an HFS, HFS Plus or HFSX volume. If there is no UUID and we were asked to generate one, then generate a new UUID and write it to the volume. Determine whether an HFS volume is mounted on the given device. If so, we need to use GetVolumeUUIDAttr and SetVolumeUUIDAttr to access the UUID through the filesystem. If there is no mounted volume, then do direct device access with GetVolumeUUIDRaw and SetVolumeUUIDRaw. Returns: FSUR_IO_SUCCESS, FSUR_IO_FAIL, FSUR_UNRECOGNIZED */ static int GetVolumeUUID(const char *deviceNamePtr, const char *rawName, VolumeUUID *volumeUUIDPtr, boolean_t generate) { int result; char *path = NULL; /* * Determine whether a volume is mounted on this device. If it is HFS, then * get the mount point's path. If it is non-HFS, then we can exit immediately * with FSUR_UNRECOGNIZED. */ result = GetHFSMountPoint(deviceNamePtr, &path); if (result != FSUR_IO_SUCCESS) goto Err_Exit; /* * Get any existing UUID. */ if (path) result = GetVolumeUUIDAttr(path, volumeUUIDPtr); else result = GetVolumeUUIDRaw(deviceNamePtr, rawName, volumeUUIDPtr); if (result != FSUR_IO_SUCCESS) goto Err_Exit; /* * If there was no valid UUID, and we were asked to generate one, then * generate it and write it back to disk. */ if (generate && (volumeUUIDPtr->v.high == 0 || volumeUUIDPtr->v.low == 0)) { GenerateVolumeUUID(volumeUUIDPtr); if (path) result = SetVolumeUUIDAttr(path, volumeUUIDPtr); else result = SetVolumeUUIDRaw(deviceNamePtr, volumeUUIDPtr); /* Fall through to Err_Exit */ } Err_Exit: return result; } /* SetVolumeUUID Write a UUID to an HFS, HFS Plus or HFSX volume. Determine whether an HFS volume is mounted on the given device. If so, we need to use SetVolumeUUIDAttr to access the UUID through the filesystem. If there is no mounted volume, then do direct device access SetVolumeUUIDRaw. Returns: FSUR_IO_SUCCESS, FSUR_IO_FAIL, FSUR_UNRECOGNIZED */ static int SetVolumeUUID(const char *deviceNamePtr, VolumeUUID *volumeUUIDPtr) { int result; char *path = NULL; /* * Determine whether a volume is mounted on this device. If it is HFS, then * get the mount point's path. If it is non-HFS, then we can exit immediately * with FSUR_UNRECOGNIZED. */ result = GetHFSMountPoint(deviceNamePtr, &path); if (result != FSUR_IO_SUCCESS) goto Err_Exit; /* * Update the UUID. */ if (path) result = SetVolumeUUIDAttr(path, volumeUUIDPtr); else result = SetVolumeUUIDRaw(deviceNamePtr, volumeUUIDPtr); Err_Exit: return result; } /* -- GetEmbeddedHFSPlusVol -- -- In: hfsMasterDirectoryBlockPtr -- Out: startOffsetPtr - the disk offset at which the HFS+ volume starts (that is, 2 blocks before the volume header) -- */ static int GetEmbeddedHFSPlusVol (HFSMasterDirectoryBlock * hfsMasterDirectoryBlockPtr, off_t * startOffsetPtr) { int result = FSUR_IO_SUCCESS; u_int32_t allocationBlockSize, firstAllocationBlock, startBlock, blockCount; if (OSSwapBigToHostInt16(hfsMasterDirectoryBlockPtr->drSigWord) != kHFSSigWord) { result = FSUR_UNRECOGNIZED; goto Return; } allocationBlockSize = OSSwapBigToHostInt32(hfsMasterDirectoryBlockPtr->drAlBlkSiz); firstAllocationBlock = OSSwapBigToHostInt16(hfsMasterDirectoryBlockPtr->drAlBlSt); if (OSSwapBigToHostInt16(hfsMasterDirectoryBlockPtr->drEmbedSigWord) != kHFSPlusSigWord) { result = FSUR_UNRECOGNIZED; goto Return; } startBlock = OSSwapBigToHostInt16(hfsMasterDirectoryBlockPtr->drEmbedExtent.startBlock); blockCount = OSSwapBigToHostInt16(hfsMasterDirectoryBlockPtr->drEmbedExtent.blockCount); if ( startOffsetPtr ) *startOffsetPtr = ((u_int64_t)startBlock * (u_int64_t)allocationBlockSize) + ((u_int64_t)firstAllocationBlock * (u_int64_t)HFS_BLOCK_SIZE); Return: return result; } /* -- GetNameFromHFSPlusVolumeStartingAt -- -- Caller's responsibility to allocate and release memory for the converted string. -- -- Returns: FSUR_IO_SUCCESS, FSUR_IO_FAIL */ static int GetNameFromHFSPlusVolumeStartingAt(int fd, off_t hfsPlusVolumeOffset, unsigned char * name_o) { int result = FSUR_IO_SUCCESS; u_int32_t blockSize; char * bufPtr = NULL; HFSPlusVolumeHeader * volHdrPtr; BTNodeDescriptor * bTreeNodeDescriptorPtr; u_int32_t catalogNodeSize; u_int32_t leafNode; u_int32_t catalogExtCount; HFSPlusExtentDescriptor *catalogExtents = NULL; volHdrPtr = (HFSPlusVolumeHeader *)malloc(HFS_BLOCK_SIZE); if ( ! volHdrPtr ) { result = FSUR_IO_FAIL; goto Return; } /* * Read the Volume Header * (This is a little redundant for a pure, unwrapped HFS+ volume) */ result = readAt( fd, volHdrPtr, hfsPlusVolumeOffset + (off_t)(2*HFS_BLOCK_SIZE), HFS_BLOCK_SIZE ); if (result == FSUR_IO_FAIL) { #if TRACE_HFS_UTIL fprintf(stderr, "hfs.util: GetNameFromHFSPlusVolumeStartingAt: readAt failed\n"); #endif goto Return; // return FSUR_IO_FAIL } /* Verify that it is an HFS+ volume. */ if (OSSwapBigToHostInt16(volHdrPtr->signature) != kHFSPlusSigWord && OSSwapBigToHostInt16(volHdrPtr->signature) != kHFSXSigWord) { result = FSUR_IO_FAIL; #if TRACE_HFS_UTIL fprintf(stderr, "hfs.util: GetNameFromHFSPlusVolumeStartingAt: volHdrPtr->signature != kHFSPlusSigWord\n"); #endif goto Return; } blockSize = OSSwapBigToHostInt32(volHdrPtr->blockSize); catalogExtents = (HFSPlusExtentDescriptor *) malloc(sizeof(HFSPlusExtentRecord)); if ( ! catalogExtents ) { result = FSUR_IO_FAIL; goto Return; } bcopy(volHdrPtr->catalogFile.extents, catalogExtents, sizeof(HFSPlusExtentRecord)); catalogExtCount = kHFSPlusExtentDensity; /* if there are overflow catalog extents, then go get them */ if (OSSwapBigToHostInt32(catalogExtents[7].blockCount) != 0) { result = GetCatalogOverflowExtents(fd, hfsPlusVolumeOffset, volHdrPtr, &catalogExtents, &catalogExtCount); if (result != FSUR_IO_SUCCESS) goto Return; } /* Read the header node of the catalog B-Tree */ result = GetBTreeNodeInfo(fd, hfsPlusVolumeOffset, blockSize, catalogExtCount, catalogExtents, &catalogNodeSize, &leafNode); if (result != FSUR_IO_SUCCESS) goto Return; /* Read the first leaf node of the catalog b-tree */ bufPtr = (char *)malloc(catalogNodeSize); if ( ! bufPtr ) { result = FSUR_IO_FAIL; goto Return; } bTreeNodeDescriptorPtr = (BTNodeDescriptor *)bufPtr; result = ReadFile(fd, bufPtr, (off_t) leafNode * (off_t) catalogNodeSize, catalogNodeSize, hfsPlusVolumeOffset, blockSize, catalogExtCount, catalogExtents); if (result == FSUR_IO_FAIL) { #if TRACE_HFS_UTIL fprintf(stderr, "hfs.util: ERROR: reading first leaf failed\n"); #endif goto Return; // return FSUR_IO_FAIL } { u_int16_t * v; char * p; HFSPlusCatalogKey * k; CFStringRef cfstr; if ( OSSwapBigToHostInt16(bTreeNodeDescriptorPtr->numRecords) < 1) { result = FSUR_IO_FAIL; #if TRACE_HFS_UTIL fprintf(stderr, "hfs.util: ERROR: bTreeNodeDescriptorPtr->numRecords < 1\n"); #endif goto Return; } // Get the offset (in bytes) of the first record from the list of offsets at the end of the node. p = bufPtr + catalogNodeSize - sizeof(u_int16_t); // pointer arithmetic in bytes v = (u_int16_t *)p; // Get a pointer to the first record. p = bufPtr + OSSwapBigToHostInt16(*v); // pointer arithmetic in bytes k = (HFSPlusCatalogKey *)p; // There should be only one record whose parent is the root parent. It should be the first record. if (OSSwapBigToHostInt32(k->parentID) != kHFSRootParentID) { result = FSUR_IO_FAIL; #if TRACE_HFS_UTIL fprintf(stderr, "hfs.util: ERROR: k->parentID != kHFSRootParentID\n"); #endif goto Return; } if ((OSSwapBigToHostInt16(k->nodeName.length) > (sizeof(k->nodeName.unicode) / sizeof(k->nodeName.unicode[0]))) || OSSwapBigToHostInt16(k->nodeName.length) > 255) { result = FSUR_IO_FAIL; #if TRACE_HFS_UTIL fprintf(stderr, "hfs.util: ERROR: k->nodeName.length is a bad size (%d)\n", OSSwapBigToHostInt16(k->nodeName.length)); #endif goto Return; } /* Extract the name of the root directory */ { HFSUniStr255 *swapped; int i; swapped = (HFSUniStr255 *)malloc(sizeof(HFSUniStr255)); if (swapped == NULL) { result = FSUR_IO_FAIL; goto Return; } swapped->length = OSSwapBigToHostInt16(k->nodeName.length); for (i=0; i<swapped->length; i++) { swapped->unicode[i] = OSSwapBigToHostInt16(k->nodeName.unicode[i]); } swapped->unicode[i] = 0; cfstr = CFStringCreateWithCharacters(kCFAllocatorDefault, swapped->unicode, swapped->length); (void) CFStringGetCString(cfstr, (char *)name_o, NAME_MAX, kCFStringEncodingUTF8); CFRelease(cfstr); free(swapped); } } result = FSUR_IO_SUCCESS; Return: if (volHdrPtr) free((char*) volHdrPtr); if (catalogExtents) free((char*) catalogExtents); if (bufPtr) free((char*)bufPtr); return result; } /* GetNameFromHFSPlusVolumeStartingAt */ typedef struct { BTNodeDescriptor node; BTHeaderRec header; } __attribute__((aligned(2), packed)) HeaderRec, *HeaderPtr; /* -- -- -- Returns: FSUR_IO_SUCCESS, FSUR_IO_FAIL -- */ static int GetBTreeNodeInfo(int fd, off_t hfsPlusVolumeOffset, u_int32_t blockSize, u_int32_t extentCount, const HFSPlusExtentDescriptor *extentList, u_int32_t *nodeSize, u_int32_t *firstLeafNode) { int result; HeaderRec * bTreeHeaderPtr = NULL; bTreeHeaderPtr = (HeaderRec *) malloc(HFS_BLOCK_SIZE); if (bTreeHeaderPtr == NULL) return (FSUR_IO_FAIL); /* Read the b-tree header node */ result = ReadFile(fd, bTreeHeaderPtr, 0, HFS_BLOCK_SIZE, hfsPlusVolumeOffset, blockSize, extentCount, extentList); if ( result == FSUR_IO_FAIL ) { #if TRACE_HFS_UTIL fprintf(stderr, "hfs.util: ERROR: reading header node failed\n"); #endif goto free; } if ( bTreeHeaderPtr->node.kind != kBTHeaderNode ) { result = FSUR_IO_FAIL; #if TRACE_HFS_UTIL fprintf(stderr, "hfs.util: ERROR: bTreeHeaderPtr->node.kind != kBTHeaderNode\n"); #endif goto free; } *nodeSize = OSSwapBigToHostInt16(bTreeHeaderPtr->header.nodeSize); if (OSSwapBigToHostInt32(bTreeHeaderPtr->header.leafRecords) == 0) *firstLeafNode = 0; else *firstLeafNode = OSSwapBigToHostInt32(bTreeHeaderPtr->header.firstLeafNode); free:; free((char*) bTreeHeaderPtr); return result; } /* GetBTreeNodeInfo */ /* -- -- -- Returns: FSUR_IO_SUCCESS, FSUR_IO_FAIL -- */ static int GetCatalogOverflowExtents(int fd, off_t hfsPlusVolumeOffset, HFSPlusVolumeHeader *volHdrPtr, HFSPlusExtentDescriptor **catalogExtents, u_int32_t *catalogExtCount) { off_t offset; u_int32_t numRecords; u_int32_t nodeSize; u_int32_t leafNode; u_int32_t blockSize; BTNodeDescriptor * bTreeNodeDescriptorPtr; HFSPlusExtentDescriptor * extents; size_t listsize; char * bufPtr = NULL; int i; int result; blockSize = OSSwapBigToHostInt32(volHdrPtr->blockSize); listsize = *catalogExtCount * sizeof(HFSPlusExtentDescriptor); extents = *catalogExtents; offset = (off_t)OSSwapBigToHostInt32(volHdrPtr->extentsFile.extents[0].startBlock) * (off_t)blockSize; /* Read the header node of the extents B-Tree */ result = GetBTreeNodeInfo(fd, hfsPlusVolumeOffset, blockSize, kHFSPlusExtentDensity, volHdrPtr->extentsFile.extents, &nodeSize, &leafNode); if (result != FSUR_IO_SUCCESS || leafNode == 0) goto Return; /* Calculate the logical position of the first leaf node */ offset = (off_t) leafNode * (off_t) nodeSize; /* Read the first leaf node of the extents b-tree */ bufPtr = (char *)malloc(nodeSize); if (! bufPtr) { result = FSUR_IO_FAIL; goto Return; } bTreeNodeDescriptorPtr = (BTNodeDescriptor *)bufPtr; again: result = ReadFile(fd, bufPtr, offset, nodeSize, hfsPlusVolumeOffset, blockSize, kHFSPlusExtentDensity, volHdrPtr->extentsFile.extents); if ( result == FSUR_IO_FAIL ) { #if TRACE_HFS_UTIL fprintf(stderr, "hfs.util: ERROR: reading first leaf failed\n"); #endif goto Return; } if (bTreeNodeDescriptorPtr->kind != kBTLeafNode) { result = FSUR_IO_FAIL; goto Return; } numRecords = OSSwapBigToHostInt16(bTreeNodeDescriptorPtr->numRecords); for (i = 1; i <= numRecords; ++i) { u_int16_t * v; char * p; HFSPlusExtentKey * k; /* * Get the offset (in bytes) of the record from the * list of offsets at the end of the node */ p = bufPtr + nodeSize - (sizeof(u_int16_t) * i); v = (u_int16_t *)p; /* Get a pointer to the record */ p = bufPtr + OSSwapBigToHostInt16(*v); /* pointer arithmetic in bytes */ k = (HFSPlusExtentKey *)p; if (OSSwapBigToHostInt32(k->fileID) != kHFSCatalogFileID) goto Return; /* grow list and copy additional extents */ listsize += sizeof(HFSPlusExtentRecord); extents = (HFSPlusExtentDescriptor *) realloc(extents, listsize); bcopy(p + OSSwapBigToHostInt16(k->keyLength) + sizeof(u_int16_t), &extents[*catalogExtCount], sizeof(HFSPlusExtentRecord)); *catalogExtCount += kHFSPlusExtentDensity; *catalogExtents = extents; } if ((leafNode = OSSwapBigToHostInt32(bTreeNodeDescriptorPtr->fLink)) != 0) { offset = (off_t) leafNode * (off_t) nodeSize; goto again; } Return:; if (bufPtr) free(bufPtr); return (result); } /* * LogicalToPhysical - Map a logical file position and size to volume-relative physical * position and number of contiguous bytes at that position. * * Inputs: * logicalOffset Logical offset in bytes from start of file * length Maximum number of bytes to map * blockSize Number of bytes per allocation block * extentCount Number of extents in file * extentList The file's extents * * Outputs: * physicalOffset Physical offset in bytes from start of volume * availableBytes Number of bytes physically contiguous (up to length) * * Returns: FSUR_IO_SUCCESS, FSUR_IO_FAIL */ static int LogicalToPhysical(off_t offset, ssize_t length, u_int32_t blockSize, u_int32_t extentCount, const HFSPlusExtentDescriptor *extentList, off_t *physicalOffset, ssize_t *availableBytes) { off_t temp; u_int32_t logicalBlock; u_int32_t extent; u_int32_t blockCount = 0; /* Determine allocation block containing logicalOffset */ logicalBlock = offset / blockSize; /* This can't overflow for valid volumes */ offset %= blockSize; /* Offset from start of allocation block */ /* Find the extent containing logicalBlock */ for (extent = 0; extent < extentCount; ++extent) { blockCount = OSSwapBigToHostInt32(extentList[extent].blockCount); if (blockCount == 0) return FSUR_IO_FAIL; /* Tried to map past physical end of file */ if (logicalBlock < blockCount) break; /* Found it! */ logicalBlock -= blockCount; } if (extent >= extentCount) return FSUR_IO_FAIL; /* Tried to map past physical end of file */ /* * When we get here, extentList[extent] is the extent containing logicalOffset. * The desired allocation block is logicalBlock blocks into the extent. */ /* Compute the physical starting position */ temp = OSSwapBigToHostInt32(extentList[extent].startBlock) + logicalBlock; /* First physical block */ temp *= blockSize; /* Byte offset of first physical block */ *physicalOffset = temp + offset; /* Compute the available contiguous bytes. */ temp = blockCount - logicalBlock; /* Number of blocks available in extent */ temp *= blockSize; temp -= offset; /* Number of bytes available */ if (temp < length) *availableBytes = temp; else *availableBytes = length; return FSUR_IO_SUCCESS; } /* * ReadFile - Read bytes from a file. Handles cases where the starting and/or * ending position are not allocation or device block aligned. * * Inputs: * fd Descriptor for reading the volume * buffer The bytes are read into here * offset Offset in file to start reading * length Number of bytes to read * volOffset Byte offset from start of device to start of volume * blockSize Number of bytes per allocation block * extentCount Number of extents in file * extentList The file's exents * * Returns: FSUR_IO_SUCCESS, FSUR_IO_FAIL */ static int ReadFile(int fd, void *buffer, off_t offset, ssize_t length, off_t volOffset, u_int32_t blockSize, u_int32_t extentCount, const HFSPlusExtentDescriptor *extentList) { int result = FSUR_IO_SUCCESS; off_t physOffset; ssize_t physLength; while (length > 0) { result = LogicalToPhysical(offset, length, blockSize, extentCount, extentList, &physOffset, &physLength); if (result != FSUR_IO_SUCCESS) break; result = readAt(fd, buffer, volOffset+physOffset, physLength); if (result != FSUR_IO_SUCCESS) break; length -= physLength; offset += physLength; buffer = (char *) buffer + physLength; } return result; } /* -- readAt = lseek() + read() -- -- Returns: FSUR_IO_SUCCESS, FSUR_IO_FAIL -- */ static ssize_t readAt( int fd, void * bufPtr, off_t offset, ssize_t length ) { int blocksize; off_t lseekResult; ssize_t readResult; void * rawData = NULL; off_t rawOffset; ssize_t rawLength; ssize_t dataOffset = 0; int result = FSUR_IO_SUCCESS; if (ioctl(fd, DKIOCGETBLOCKSIZE, &blocksize) < 0) { #if TRACE_HFS_UTIL fprintf(stderr, "hfs.util: readAt: couldn't determine block size of device.\n"); #endif result = FSUR_IO_FAIL; goto Return; } /* put offset and length in terms of device blocksize */ rawOffset = offset / blocksize * blocksize; dataOffset = offset - rawOffset; rawLength = ((length + dataOffset + blocksize - 1) / blocksize) * blocksize; rawData = malloc(rawLength); if (rawData == NULL) { result = FSUR_IO_FAIL; goto Return; } lseekResult = lseek( fd, rawOffset, SEEK_SET ); if ( lseekResult != rawOffset ) { result = FSUR_IO_FAIL; goto Return; } readResult = read(fd, rawData, rawLength); if ( readResult != rawLength ) { #if TRACE_HFS_UTIL fprintf(stderr, "hfs.util: readAt: attempt to read data from device failed (errno = %d)?\n", errno); #endif result = FSUR_IO_FAIL; goto Return; } bcopy(rawData + dataOffset, bufPtr, length); Return: if (rawData) { free(rawData); } return result; } /* readAt */ /* -- writeAt = lseek() + write() -- -- Returns: FSUR_IO_SUCCESS, FSUR_IO_FAIL -- */ static ssize_t writeAt( int fd, void * bufPtr, off_t offset, ssize_t length ) { int blocksize; off_t deviceoffset; ssize_t bytestransferred; void * rawData = NULL; off_t rawOffset; ssize_t rawLength; ssize_t dataOffset = 0; int result = FSUR_IO_SUCCESS; if (ioctl(fd, DKIOCGETBLOCKSIZE, &blocksize) < 0) { #if TRACE_HFS_UTIL fprintf(stderr, "hfs.util: couldn't determine block size of device.\n"); #endif result = FSUR_IO_FAIL; goto Return; } /* put offset and length in terms of device blocksize */ rawOffset = offset / blocksize * blocksize; dataOffset = offset - rawOffset; rawLength = ((length + dataOffset + blocksize - 1) / blocksize) * blocksize; rawData = malloc(rawLength); if (rawData == NULL) { result = FSUR_IO_FAIL; goto Return; } deviceoffset = lseek( fd, rawOffset, SEEK_SET ); if ( deviceoffset != rawOffset ) { result = FSUR_IO_FAIL; goto Return; } /* If the write isn't block-aligned, read the existing data before writing the new data: */ if (((rawOffset % blocksize) != 0) || ((rawLength % blocksize) != 0)) { bytestransferred = read(fd, rawData, rawLength); if ( bytestransferred != rawLength ) { #if TRACE_HFS_UTIL fprintf(stderr, "writeAt: attempt to pre-read data from device failed (errno = %d)\n", errno); #endif result = FSUR_IO_FAIL; goto Return; } } bcopy(bufPtr, rawData + dataOffset, length); /* Copy in the new data */ deviceoffset = lseek( fd, rawOffset, SEEK_SET ); if ( deviceoffset != rawOffset ) { result = FSUR_IO_FAIL; goto Return; } bytestransferred = write(fd, rawData, rawLength); if ( bytestransferred != rawLength ) { #if TRACE_HFS_UTIL fprintf(stderr, "writeAt: attempt to write data to device failed?!"); #endif result = FSUR_IO_FAIL; goto Return; } Return: if (rawData) free(rawData); return result; } /* writeAt */ /* * Get kernel's encoding bias. */ static int GetEncodingBias() { int mib[3]; size_t buflen = sizeof(int); struct vfsconf vfc; int hint = 0; if (getvfsbyname("hfs", &vfc) < 0) goto error; mib[0] = CTL_VFS; mib[1] = vfc.vfc_typenum; mib[2] = HFS_ENCODINGBIAS; if (sysctl(mib, 3, &hint, &buflen, NULL, 0) < 0) goto error; return (hint); error: return (-1); } /****************************************************************************** * * V O L U M E S T A T U S D A T A B A S E R O U T I N E S * *****************************************************************************/ #define DBHANDLESIGNATURE 0x75917737 /* Flag values for operation options: */ #define DBMARKPOSITION 1 static char gVSDBPath[] = "/var/db/volinfo.database"; #define MAXIOMALLOC 16384 /* Database layout: */ struct VSDBKey { char uuid[16]; }; struct VSDBRecord { char statusFlags[8]; }; struct VSDBEntry { struct VSDBKey key; char keySeparator; char space; struct VSDBRecord record; char terminator; }; #define DBKEYSEPARATOR ':' #define DBBLANKSPACE ' ' #define DBRECORDTERMINATOR '\n' /* In-memory data structures: */ struct VSDBState { unsigned long signature; int dbfile; int dbmode; off_t recordPosition; }; typedef struct VSDBState *VSDBStatePtr; /* Internal function prototypes: */ static int LockDB(VSDBStatePtr dbstateptr, int lockmode); static int UnlockDB(VSDBStatePtr dbstateptr); static int FindVolumeRecordByUUID(VSDBStatePtr dbstateptr, VolumeUUID *volumeID, struct VSDBEntry *dbentry, unsigned long options); static int AddVolumeRecord(VSDBStatePtr dbstateptr, struct VSDBEntry *dbentry); static int UpdateVolumeRecord(VSDBStatePtr dbstateptr, struct VSDBEntry *dbentry); static int GetVSDBEntry(VSDBStatePtr dbstateptr, struct VSDBEntry *dbentry); static int CompareVSDBKeys(struct VSDBKey *key1, struct VSDBKey *key2); static void FormatULong(unsigned long u, char *s); static void FormatUUID(VolumeUUID *volumeID, char *UUIDField); static void FormatDBKey(VolumeUUID *volumeID, struct VSDBKey *dbkey); static void FormatDBRecord(unsigned long volumeStatusFlags, struct VSDBRecord *dbrecord); static void FormatDBEntry(VolumeUUID *volumeID, unsigned long volumeStatusFlags, struct VSDBEntry *dbentry); static unsigned long ConvertHexStringToULong(const char *hs, long maxdigits); /****************************************************************************** * * P U B L I S H E D I N T E R F A C E R O U T I N E S * *****************************************************************************/ void GenerateVolumeUUID(VolumeUUID *newVolumeID) { SHA_CTX context; char randomInputBuffer[26]; unsigned char digest[20]; time_t now; clock_t uptime; int mib[2]; int sysdata; char sysctlstring[128]; size_t datalen; double sysloadavg[3]; struct vmtotal sysvmtotal; do { /* Initialize the SHA-1 context for processing: */ SHA1_Init(&context); /* Now process successive bits of "random" input to seed the process: */ /* The current system's uptime: */ uptime = clock(); SHA1_Update(&context, &uptime, sizeof(uptime)); /* The kernel's boot time: */ mib[0] = CTL_KERN; mib[1] = KERN_BOOTTIME; datalen = sizeof(sysdata); sysctl(mib, 2, &sysdata, &datalen, NULL, 0); SHA1_Update(&context, &sysdata, datalen); /* The system's host id: */ mib[0] = CTL_KERN; mib[1] = KERN_HOSTID; datalen = sizeof(sysdata); sysctl(mib, 2, &sysdata, &datalen, NULL, 0); SHA1_Update(&context, &sysdata, datalen); /* The system's host name: */ mib[0] = CTL_KERN; mib[1] = KERN_HOSTNAME; datalen = sizeof(sysctlstring); sysctl(mib, 2, sysctlstring, &datalen, NULL, 0); SHA1_Update(&context, sysctlstring, datalen); /* The running kernel's OS release string: */ mib[0] = CTL_KERN; mib[1] = KERN_OSRELEASE; datalen = sizeof(sysctlstring); sysctl(mib, 2, sysctlstring, &datalen, NULL, 0); SHA1_Update(&context, sysctlstring, datalen); /* The running kernel's version string: */ mib[0] = CTL_KERN; mib[1] = KERN_VERSION; datalen = sizeof(sysctlstring); sysctl(mib, 2, sysctlstring, &datalen, NULL, 0); SHA1_Update(&context, sysctlstring, datalen); /* The system's load average: */ datalen = sizeof(sysloadavg); getloadavg(sysloadavg, 3); SHA1_Update(&context, &sysloadavg, datalen); /* The system's VM statistics: */ mib[0] = CTL_VM; mib[1] = VM_METER; datalen = sizeof(sysvmtotal); sysctl(mib, 2, &sysvmtotal, &datalen, NULL, 0); SHA1_Update(&context, &sysvmtotal, datalen); /* The current GMT (26 ASCII characters): */ time(&now); strncpy(randomInputBuffer, asctime(gmtime(&now)), 26); /* "Mon Mar 27 13:46:26 2000" */ SHA1_Update(&context, randomInputBuffer, 26); /* Pad the accumulated input and extract the final digest hash: */ SHA1_Final(digest, &context); memcpy(newVolumeID, digest, sizeof(*newVolumeID)); } while ((newVolumeID->v.high == 0) || (newVolumeID->v.low == 0)); } void ConvertVolumeUUIDStringToUUID(const char *UUIDString, VolumeUUID *volumeID) { int i; char c; u_int32_t nextdigit; u_int32_t high = 0; u_int32_t low = 0; u_int32_t carry; for (i = 0; (i < VOLUMEUUIDLENGTH) && ((c = UUIDString[i]) != (char)0) ; ++i) { if ((c >= '0') && (c <= '9')) { nextdigit = c - '0'; } else if ((c >= 'A') && (c <= 'F')) { nextdigit = c - 'A' + 10; } else if ((c >= 'a') && (c <= 'f')) { nextdigit = c - 'a' + 10; } else { nextdigit = 0; } carry = ((low & 0xF0000000) >> 28) & 0x0000000F; high = (high << 4) | carry; low = (low << 4) | nextdigit; } volumeID->v.high = high; volumeID->v.low = low; } void ConvertVolumeUUIDToString(VolumeUUID *volumeID, char *UUIDString) { FormatUUID(volumeID, UUIDString); *(UUIDString+16) = (char)0; /* Append a terminating null character */ } int OpenVolumeStatusDB(VolumeStatusDBHandle *DBHandlePtr) { VSDBStatePtr dbstateptr; *DBHandlePtr = NULL; dbstateptr = (VSDBStatePtr)malloc(sizeof(*dbstateptr)); if (dbstateptr == NULL) { return ENOMEM; } dbstateptr->dbmode = O_RDWR; dbstateptr->dbfile = open(gVSDBPath, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); if (dbstateptr->dbfile == -1) { /* The file couldn't be opened for read/write access: try read-only access before giving up altogether. */ dbstateptr->dbmode = O_RDONLY; dbstateptr->dbfile = open(gVSDBPath, O_RDONLY | O_CREAT, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); if (dbstateptr->dbfile == -1) { return errno; } } dbstateptr->signature = DBHANDLESIGNATURE; *DBHandlePtr = (VolumeStatusDBHandle)dbstateptr; return 0; } int GetVolumeStatusDBEntry(VolumeStatusDBHandle DBHandle, VolumeUUID *volumeID, unsigned long *VolumeStatus) { VSDBStatePtr dbstateptr = (VSDBStatePtr)DBHandle; struct VSDBEntry dbentry; int result; if (dbstateptr->signature != DBHANDLESIGNATURE) return EINVAL; if ((result = LockDB(dbstateptr, LOCK_SH)) != 0) return result; if ((result = FindVolumeRecordByUUID(dbstateptr, volumeID, &dbentry, 0)) != 0) { goto ErrExit; } *VolumeStatus = VOLUME_RECORDED | ConvertHexStringToULong(dbentry.record.statusFlags, sizeof(dbentry.record.statusFlags)); result = 0; ErrExit: UnlockDB(dbstateptr); return result; } int SetVolumeStatusDBEntry(VolumeStatusDBHandle DBHandle, VolumeUUID *volumeID, unsigned long VolumeStatus) { VSDBStatePtr dbstateptr = (VSDBStatePtr)DBHandle; struct VSDBEntry dbentry; int result; if (dbstateptr->signature != DBHANDLESIGNATURE) return EINVAL; if (VolumeStatus & ~VOLUME_VALIDSTATUSBITS) return EINVAL; if ((result = LockDB(dbstateptr, LOCK_EX)) != 0) return result; FormatDBEntry(volumeID, VolumeStatus, &dbentry); if ((result = FindVolumeRecordByUUID(dbstateptr, volumeID, NULL, DBMARKPOSITION)) == 0) { #if DEBUG_TRACE fprintf(stderr,"AddLocalVolumeUUID: found record in database; updating in place.\n"); #endif result = UpdateVolumeRecord(dbstateptr, &dbentry); } else if (result == -1) { #if DEBUG_TRACE fprintf(stderr,"AddLocalVolumeUUID: record not found in database; appending at end.\n"); #endif result = AddVolumeRecord(dbstateptr, &dbentry); } else { goto ErrExit; } fsync(dbstateptr->dbfile); result = 0; ErrExit: UnlockDB(dbstateptr); return result; } int DeleteVolumeStatusDBEntry(VolumeStatusDBHandle DBHandle, VolumeUUID *volumeID) { VSDBStatePtr dbstateptr = (VSDBStatePtr)DBHandle; struct stat dbinfo; int result; unsigned long iobuffersize; void *iobuffer = NULL; off_t dataoffset; unsigned long iotransfersize; unsigned long bytestransferred; if (dbstateptr->signature != DBHANDLESIGNATURE) return EINVAL; if ((result = LockDB(dbstateptr, LOCK_EX)) != 0) return result; if ((result = FindVolumeRecordByUUID(dbstateptr, volumeID, NULL, DBMARKPOSITION)) != 0) { #if DEBUG_TRACE fprintf(stderr, "DeleteLocalVolumeUUID: No record with matching volume UUID in DB (result = %d).\n", result); #endif if (result == -1) result = 0; /* Entry wasn't in the database to begin with? */ goto StdEdit; } else { #if DEBUG_TRACE fprintf(stderr, "DeleteLocalVolumeUUID: Found record with matching volume UUID...\n"); #endif if ((result = stat(gVSDBPath, &dbinfo)) != 0) goto ErrExit; if ((dbinfo.st_size - dbstateptr->recordPosition - sizeof(struct VSDBEntry)) <= MAXIOMALLOC) { iobuffersize = dbinfo.st_size - dbstateptr->recordPosition - sizeof(struct VSDBEntry); } else { iobuffersize = MAXIOMALLOC; } #if DEBUG_TRACE fprintf(stderr, "DeleteLocalVolumeUUID: DB size = 0x%08lx; recordPosition = 0x%08lx;\n", (unsigned long)dbinfo.st_size, (unsigned long)dbstateptr->recordPosition); fprintf(stderr, "DeleteLocalVolumeUUID: I/O buffer size = 0x%lx\n", iobuffersize); #endif if (iobuffersize > 0) { iobuffer = malloc(iobuffersize); if (iobuffer == NULL) { result = ENOMEM; goto ErrExit; } dataoffset = dbstateptr->recordPosition + sizeof(struct VSDBEntry); do { iotransfersize = dbinfo.st_size - dataoffset; if (iotransfersize > 0) { if (iotransfersize > iobuffersize) iotransfersize = iobuffersize; #if DEBUG_TRACE fprintf(stderr, "DeleteLocalVolumeUUID: reading 0x%08lx bytes starting at 0x%08lx ...\n", iotransfersize, (unsigned long)dataoffset); #endif lseek(dbstateptr->dbfile, dataoffset, SEEK_SET); bytestransferred = read(dbstateptr->dbfile, iobuffer, iotransfersize); if (bytestransferred != iotransfersize) { result = errno; goto ErrExit; } #if DEBUG_TRACE fprintf(stderr, "DeleteLocalVolumeUUID: writing 0x%08lx bytes starting at 0x%08lx ...\n", iotransfersize, (unsigned long)(dataoffset - (off_t)sizeof(struct VSDBEntry))); #endif lseek(dbstateptr->dbfile, dataoffset - (off_t)sizeof(struct VSDBEntry), SEEK_SET); bytestransferred = write(dbstateptr->dbfile, iobuffer, iotransfersize); if (bytestransferred != iotransfersize) { result = errno; goto ErrExit; } dataoffset += (off_t)iotransfersize; } } while (iotransfersize > 0); } #if DEBUG_TRACE fprintf(stderr, "DeleteLocalVolumeUUID: truncating database file to 0x%08lx bytes.\n", (unsigned long)(dbinfo.st_size - (off_t)(sizeof(struct VSDBEntry)))); #endif if ((result = ftruncate(dbstateptr->dbfile, dbinfo.st_size - (off_t)(sizeof(struct VSDBEntry)))) != 0) { goto ErrExit; } fsync(dbstateptr->dbfile); result = 0; } ErrExit: if (iobuffer) free(iobuffer); UnlockDB(dbstateptr); StdEdit: return result; } int CloseVolumeStatusDB(VolumeStatusDBHandle DBHandle) { VSDBStatePtr dbstateptr = (VSDBStatePtr)DBHandle; if (dbstateptr->signature != DBHANDLESIGNATURE) return EINVAL; dbstateptr->signature = 0; close(dbstateptr->dbfile); /* Nothing we can do about any errors... */ dbstateptr->dbfile = 0; free(dbstateptr); return 0; } /****************************************************************************** * * I N T E R N A L O N L Y D A T A B A S E R O U T I N E S * *****************************************************************************/ static int LockDB(VSDBStatePtr dbstateptr, int lockmode) { #if DEBUG_TRACE fprintf(stderr, "LockDB: Locking VSDB file...\n"); #endif return flock(dbstateptr->dbfile, lockmode); } static int UnlockDB(VSDBStatePtr dbstateptr) { #if DEBUG_TRACE fprintf(stderr, "UnlockDB: Unlocking VSDB file...\n"); #endif return flock(dbstateptr->dbfile, LOCK_UN); } static int FindVolumeRecordByUUID(VSDBStatePtr dbstateptr, VolumeUUID *volumeID, struct VSDBEntry *targetEntry, unsigned long options) { struct VSDBKey searchkey; struct VSDBEntry dbentry; int result; FormatDBKey(volumeID, &searchkey); lseek(dbstateptr->dbfile, 0, SEEK_SET); do { result = GetVSDBEntry(dbstateptr, &dbentry); if ((result == 0) && (CompareVSDBKeys(&dbentry.key, &searchkey) == 0)) { if (targetEntry != NULL) { #if DEBUG_TRACE fprintf(stderr, "FindVolumeRecordByUUID: copying %d. bytes from %08xl to %08l...\n", sizeof(*targetEntry), &dbentry, targetEntry); #endif memcpy(targetEntry, &dbentry, sizeof(*targetEntry)); } return 0; } } while (result == 0); return -1; } static int AddVolumeRecord(VSDBStatePtr dbstateptr , struct VSDBEntry *dbentry) { #if DEBUG_TRACE VolumeUUIDString id; #endif #if DEBUG_TRACE strncpy(id, dbentry->key.uuid, sizeof(dbentry->key.uuid)); id[sizeof(dbentry->key.uuid)] = (char)0; fprintf(stderr, "AddVolumeRecord: Adding record for UUID #%s...\n", id); #endif lseek(dbstateptr->dbfile, 0, SEEK_END); return write(dbstateptr->dbfile, dbentry, sizeof(struct VSDBEntry)); } static int UpdateVolumeRecord(VSDBStatePtr dbstateptr, struct VSDBEntry *dbentry) { #if DEBUG_TRACE VolumeUUIDString id; #endif #if DEBUG_TRACE strncpy(id, dbentry->key.uuid, sizeof(dbentry->key.uuid)); id[sizeof(dbentry->key.uuid)] = (char)0; fprintf(stderr, "UpdateVolumeRecord: Updating record for UUID #%s at offset 0x%08lx in database...\n", id, (unsigned long)dbstateptr->recordPosition); #endif lseek(dbstateptr->dbfile, dbstateptr->recordPosition, SEEK_SET); #if DEBUG_TRACE fprintf(stderr, "UpdateVolumeRecord: Writing %d. bytes...\n", sizeof(*dbentry)); #endif return write(dbstateptr->dbfile, dbentry, sizeof(*dbentry)); } static int GetVSDBEntry(VSDBStatePtr dbstateptr, struct VSDBEntry *dbentry) { struct VSDBEntry entry; int result; #if DEBUG_TRACE VolumeUUIDString id; #endif dbstateptr->recordPosition = lseek(dbstateptr->dbfile, 0, SEEK_CUR); #if 0 // DEBUG_TRACE fprintf(stderr, "GetVSDBEntry: starting reading record at offset 0x%08lx...\n", (unsigned long)dbstateptr->recordPosition); #endif result = read(dbstateptr->dbfile, &entry, sizeof(entry)); if ((result != sizeof(entry)) || (entry.keySeparator != DBKEYSEPARATOR) || (entry.space != DBBLANKSPACE) || (entry.terminator != DBRECORDTERMINATOR)) { return -1; } #if DEBUG_TRACE strncpy(id, entry.key.uuid, sizeof(entry.key.uuid)); id[sizeof(entry.key.uuid)] = (char)0; fprintf(stderr, "GetVSDBEntry: returning entry for UUID #%s...\n", id); #endif memcpy(dbentry, &entry, sizeof(*dbentry)); return 0; } static int CompareVSDBKeys(struct VSDBKey *key1, struct VSDBKey *key2) { #if 0 // DEBUG_TRACE VolumeUUIDString id; strncpy(id, key1->uuid, sizeof(key1->uuid)); id[sizeof(key1->uuid)] = (char)0; fprintf(stderr, "CompareVSDBKeys: comparing #%s to ", id); strncpy(id, key2->uuid, sizeof(key2->uuid)); id[sizeof(key2->uuid)] = (char)0; fprintf(stderr, "%s (%d.)...\n", id, sizeof(key1->uuid)); #endif return memcmp(key1->uuid, key2->uuid, sizeof(key1->uuid)); } /****************************************************************************** * * F O R M A T T I N G A N D C O N V E R S I O N R O U T I N E S * *****************************************************************************/ static void FormatULong(unsigned long u, char *s) { unsigned long d; int i; char *digitptr = s; for (i = 0; i < 8; ++i) { d = ((u & 0xF0000000) >> 28) & 0x0000000F; if (d < 10) { *digitptr++ = (char)(d + '0'); } else { *digitptr++ = (char)(d - 10 + 'A'); } u = u << 4; } } static void FormatUUID(VolumeUUID *volumeID, char *UUIDField) { FormatULong(volumeID->v.high, UUIDField); FormatULong(volumeID->v.low, UUIDField+8); } static void FormatDBKey(VolumeUUID *volumeID, struct VSDBKey *dbkey) { FormatUUID(volumeID, dbkey->uuid); } static void FormatDBRecord(unsigned long volumeStatusFlags, struct VSDBRecord *dbrecord) { FormatULong(volumeStatusFlags, dbrecord->statusFlags); } static void FormatDBEntry(VolumeUUID *volumeID, unsigned long volumeStatusFlags, struct VSDBEntry *dbentry) { FormatDBKey(volumeID, &dbentry->key); dbentry->keySeparator = DBKEYSEPARATOR; dbentry->space = DBBLANKSPACE; FormatDBRecord(volumeStatusFlags, &dbentry->record); #if 0 // DEBUG_TRACE dbentry->terminator = (char)0; fprintf(stderr, "FormatDBEntry: '%s' (%d.)\n", dbentry, sizeof(*dbentry)); #endif dbentry->terminator = DBRECORDTERMINATOR; } static unsigned long ConvertHexStringToULong(const char *hs, long maxdigits) { int i; char c; unsigned long nextdigit; unsigned long n; n = 0; for (i = 0; (i < 8) && ((c = hs[i]) != (char)0) ; ++i) { if ((c >= '0') && (c <= '9')) { nextdigit = c - '0'; } else if ((c >= 'A') && (c <= 'F')) { nextdigit = c - 'A' + 10; } else if ((c >= 'a') && (c <= 'f')) { nextdigit = c - 'a' + 10; } else { nextdigit = 0; } n = (n << 4) + nextdigit; } return n; }

WARC-Type: response

Content-Type: application/http; msgtype=response

Accept-Ranges: bytes

Connection: close

Content-Type: text/html; charset=utf-8


markdown:
Tìm kiếm
Thành viên
Lịch
Trợ gúp
Thời gian hiện tại :
22-10-2016, 06:15 AM
Xin chào quý khách! (
Đăng nhập
—
Đăng ký
)
VÀO DIỄN ĐÀN
/
Thông tin của noyt1005
noyt1005
(Account not Activated)
Ngày đăng ký:
10-02-2016
Sinh nhật:
Không rõ
Giờ khu vực:
22-10-2016 lúc 06:15 AM
Tình trạng:
Offline
Thông tin của noyt1005
Tham gia:
10-02-2016
Lần đăng nhập mới
10-02-2016 12:59 PM
Tổng bài viết:
0 (0 bài viết mỗi ngày | 0% bài viết mỗi ngày)
(
Chủ đề thành viên này
—
Bài viết thành viên này
)
Thời gian duyệt:
9 Phút
Chi tiết liên lạc của noyt1005
Trang chủ:
Hộp thư:
Gửi cho noyt1005 1 email.
Tin cá nhân:
ICQ:
AIM:
Yahoo:
MSN:
Thông tin cá nhân của noyt1005
Sex:
Location:
Bio:
Chữ ký của noyt1005
تور لحظه آخری ارزان
Liên hệ
|
Your Website
|
Lên trên
|
Nội dung
|
Bản rút gọn
|
Tin RSS
|
Mã nguồn bởi
MyBB 1.4.11
, © 2002-2016
MyBB Group
.
Bản quyền thuộc về Phòng Giáo dục và Đào tạo huyện Ngọc Hồi, tỉnh Kon Tum
- Designed by
MyBBThemes
tai camera 360

Set-Cookie: sid=cb6d1f60c1a78df92aa463f54662b6bf; path=/forum/; domain=.pgdngochoi.edu.vn; HttpOnly

Server: nginx

Vary: Accept-Encoding,User-Agent

Content-Type: text/html; charset=UTF-8


markdown: Direct Download (100% Working Link) Theo thông tin từ người thân ông N., hiện sức khỏe của ông đã ổn định trở lại, ông vẫn tỉnh táo và nhận thức được mọi việc. Liên quan tới sức khỏe ông N.V.N. (87 tuổi) vừa xuất viện đã bị các con cho nằm vỉa hè gần 10 tiếng đồng hồ, theo thông tin từ người thân của ông N., hiện tại sức khỏe của ông đã tốt hơn nhiều, dù còn yếu, nhưng ông vẫn tỉnh táo, mọi sự việc xảy ra ngày 7/9 ông đều biết và nhớ rõ. Hiện tại, ông N. vẫn đang được chăm sóc ở nhà người con gái thứ 2. Theo phân công, đáng lẽ ông N. sẽ sống với người con trai út, ở nhà trên đường Bưởi. Nhưng căn nhà này của ông bà đã được cho thuê, còn người con trai út đã về sống bên nhà vợ trên phố Đội Cấn. Ông N. phải nằm trên vỉa hè ẩm ướt, ngoài trời khi nắng, lúc mưa. Còn bà H. - con dâu cả của ông N., theo thông tin chúng tôi có được, sau khi xảy ra vụ việc bà đã mấy đêm liền mất ngủ, nên cơ thể bị suy nhược, đổ bệnh. Chia sẻ với chúng tôi, người cháu gái của ông N. cho hay: “Em không thể hiểu nổi hai người cô của em sao lại làm thế được, họ đều là những người có học, một người là Y tá Bệnh viện Mắt, một người hiện đang làm kế toán cho trường Trung cấp Sư phạm mẫu giáo. Các cô thực sự đã nghĩ gì khi hành động dã man, vô nhân tính và thiếu suy nghĩ như vậy. Trong khi bản thân đều đã và đang công tác ở những ngành nghề rất cần đến một tấm lòng, ở những nơi mà lương tâm phải hoàn toàn hướng thiện. Vậy mà, các cô, chú đã nghĩ gì khi đùn đẩy trách nhiệm chăm sóc bố mẹ về cả cho chị dâu và cháu gái, còn đem bố ra làm công cụ để ép người?”. Phạm Thanh Nguồn : Phunutoday

Cache-control: private, max-age=0

Server: LiteSpeed

X-Frame-Options: SAMEORIGIN

Vary: Accept-Encoding


markdown:
Phim8k.Net
Trang chủ
Thể loại
Phim Hành Động
Phim Viễn Tưởng
Phim Chiến Tranh
Phim Hình Sự
Phim Phiêu Lưu
Phim Hài Hước
Phim Võ Thuật
Phim Hoạt Hình
Phim Kinh Dị
Phim Thần Thoại
Phim Tâm Lý
Phim Âm Nhạc
Phim Tình Cảm
Phim Tội Phạm
Phim 18+
Quốc gia
Việt Nam
Trung Quốc
Mỹ
Hàn Quốc
Nhật Bản
Hồng Kông
Ấn Độ
Thái Lan
Đài Loan
Pháp
Xem phim mới
Phim 2016
Phim 2015
Phim 2014
Phim 2013
Phim 2012
Phim 2011
Phim 2010
Phim 2009
Phim 2008
Phim 2007
Phim 2006
Phim 2005
Phim 2004
Phim 2003
Phim 2002
Phim 2001
Phim 2000
Phim 1999
Phim 1998
Phim 1997
Phim 1996
Phim lẻ
Phim lẻ 2016
Phim lẻ 2015
Phim lẻ 2014
Phim lẻ 2013
Phim lẻ 2012
Phim lẻ 2011
Phim lẻ 2010
Phim lẻ 2009
Phim lẻ 2008
Phim lẻ 2007
Phim lẻ 2006
Phim lẻ 2005
Phim lẻ 2004
Phim lẻ 2003
Phim lẻ 2002
Phim lẻ 2001
Phim lẻ 2000
Phim bộ
Phim chiếu rạp
Diễn Viên
Hài Tết
Hài Tết 2016
Trang chủ
Diễn viên
Jason Momoa
Jason Momoa
Tên khác:
Ngày sinh:
Nghề nghiệp:
Diễn viên
Quốc gia:
Chiều cao:
Cân nặng:
Chưa có thông tin
Nhóm máu:
Chưa có thông tin
Ngôn ngữ:
Chưa có thông tin
Sở trường:
Chưa có thông tin
Sở thích:
Chưa có thông tin
Tiểu sử
Một số phim đã tham gia
Liên Minh Công Lý
Justice League
Trailer
Đường Đến Paloma
Road To Paloma
HD
Phim8k.Net
Trailer phim mới
Dòng Dõi Sát Thủ
Assassins Creed
Lượt xem: 14134
Nữ Siêu Nhân
Wonder Woman
Lượt xem: 11938
Liên Minh Công Lý
Justice League
Lượt xem: 5080
Hộ Vệ
Guardians (zashchitniki)
Lượt xem: 2169
Vùng Đất Quỷ Dữ 6
Resident Evil 6: The Final Chapter
Lượt xem: 1991
Chiếc Xe Tải Quái Vật
Monster Trucks
Lượt xem: 1234
Phù Thủy Tối Thượng
Dr. Strange
Lượt xem: 1056
Quỷ Lùn
Trolls
Lượt xem: 751
Stonewall
Stonewall
Lượt xem: 2006
Đại Ngọc Nhi Truyền Kỳ
The Legend Of Da Yu Er
Lượt xem: 3359
Phim xem nhiều trong ngày
Tân Kim Bình Mai 3d
Tan Kim Binh Mai 3d
Lượt xem: 3495
Gian Hàng 18
Street Stall
Lượt xem: 2068
Người Cha Thay Thế
A Surrogate Father
Lượt xem: 1794
Nhục Bồ Đoàn 2
Sex And Zen 2
Lượt xem: 1594
Vợ Hi Sinh Chiều Sếp Để Chồng Thăng Tiến...
The Couple Sponsor
Lượt xem: 1424
Già Mà Ham
Woman War
Lượt xem: 1383
Phim Nghiệp Làm Gái
Sara 18+
Lượt xem: 1191
Người Chị Trẻ
Young Sister In Law
Lượt xem: 1146
Tâm Sự Cô Nàng Nghiện Sex
Monamour
Lượt xem: 1122
Chuyện Tình Nơi Công Sở - Nữ Nhân Viên Gợi Tình
The Female Workers
Lượt xem: 1022
Phim lẻ hot trong tuần
Vợ Hi Sinh Chiều Sếp Để Chồng Thăng Tiến...
The Couple Sponsor
Lượt xem: 37864
Tân Kim Bình Mai 3d
Tan Kim Binh Mai 3d
Lượt xem: 988883
Người Cha Thay Thế
A Surrogate Father
Lượt xem: 33544
Gian Hàng 18
Street Stall
Lượt xem: 30900
Phim Nghiệp Làm Gái
Sara 18+
Lượt xem: 40353
Già Mà Ham
Woman War
Lượt xem: 23321
Nàng Thơ
A Muse
Lượt xem: 19283
Nhục Bồ Đoàn 2
Sex And Zen 2
Lượt xem: 325479
Chuyện Tình Nơi Công Sở - Nữ Nhân Viên Gợi Tình
The Female Workers
Lượt xem: 18089
Em Hàng Xóm / Chị Gái Phòng Bên
Sister Next Door
Lượt xem: 161094
Phim bộ hot trong tuần
Bóng Tối Trái Tim
Ngao Jai
Lượt xem: 29103
Khoai To (phần 1)
Hung (season 1)
Lượt xem: 104237
Khoai To (phần 2)
Hung Season 2
Lượt xem: 64387
Khuyển Dạ Xoa
Inuyasha
Lượt xem: 47454
7 Viên Ngọc Rồng - Dragon Ball Z Ngoại Truyện
Dragon Ball Z Ngoại Truyện
Lượt xem: 23350
Nhất Đại Võ Thần
God Of War
Lượt xem: 41402
Võ Tắc Thiên Truyền Kỳ
The Empress Of China
Lượt xem: 21199
Spartacus Phần 1: Máu Và Cát
Spartacus Season 1: Blood And Sand
Lượt xem: 6255
Truyền Thuyết Ju Mông
Jumong
Lượt xem: 31253
Nữ Vương Học Đường (phần 1)
Majisuka Gakuen (season 1)
Lượt xem: 8071
Hồ sơ diễn viên
Kang Min Hyuk (Cnblue)
Kim Hye-Yoon

WARC-Truncated: length

Server: cloudflare-nginx

Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0

Pragma: no-cache


markdown:
1
![ 2 Thế Giới](http://phimcoi.net/upload/phim/201608/xem-va-tai-phim-full-hd-2-The-GioiW-Two-Worlds-1470322905.jpg)
[2 Thế Giới](phim-bo/2-the-gioi/)60 phút/tập
2
![ Lọ lem và Tứ kỵ](http://phimcoi.net/upload/phim/201608/xem-va-tai-phim-full-hd-Lo-lem-va-Tu-kyCinderella-and-Four-Knights-1472359138.jpg)
[Lọ lem và Tứ kỵ](phim-bo/lo-lem-va-tu-ky/)60 phút/16 tập
3
![ Tử Thần Ngọt Ngào Trọn...](http://phimcoi.net/upload/phim/201401/xem-va-tai-phim-full-hd-Tu-Than-Ngot-Ngao-Tron-Bo-1390485837.jpg)
[Tử Thần Ngọt Ngào Trọn...](phim-bo/tu-than-ngot-ngao-tron-bo/)15/15 Tập
4
![ Pháp Võng Truy Kích](http://phimcoi.net/upload/phim/201511/xem-va-tai-phim-full-hd-Phap-Vong-Truy-Kich-1448465900.jpg)
[Pháp Võng Truy Kích](phim-bo/phap-vong-truy-kich/)24 Tập
5
![ Oan Gia Ngõ Hẹp You...](http://phimcoi.net/upload/phim/201608/xem-va-tai-phim-full-hd-Oan-Gia-Ngo-Hep-You-TV-1470924423.jpg)
[Oan Gia Ngõ Hẹp You...](phim-bo/oan-gia-ngo-hep-you-tv/)24 Tập
6
![ Có Tiền Có Tình Yêu...](http://phimcoi.net/upload/phim/201406/xem-va-tai-phim-full-hd-Co-Tien-Co-Tinh-Yeu-1404061182.jpg)
[Có Tiền Có Tình Yêu...](phim-bo/co-tien-co-tinh-yeu/)20 tập
7
![ Chiến nào ma kia](http://phimcoi.net/upload/phim/201608/xem-va-tai-phim-full-hd-Chien-nao-ma-kiaLets-Fight-Ghost-1470399591.jpg)
[Chiến nào ma kia](phim-bo/chien-nao-ma-kia/)60 phút/tập
8
![ Bí mật trong lớp học](http://phimcoi.net/upload/phim/201608/xem-va-tai-phim-full-hd-Bi-mat-trong-lop-hocSecret-Of-Classroom-1470320274.jpg)
[Bí mật trong lớp học](phim-bo/bi-mat-trong-lop-hoc/)45 phút/tập
9
![ Chuyện Tình Bác Sĩ](http://phimcoi.net/upload/phim/201607/xem-va-tai-phim-full-hd-Chuyen-Tinh-Bac-SiDoctors-1469860378.jpg)
[Chuyện Tình Bác Sĩ](phim-bo/chuyen-tinh-bac-si/)20 tập
10
![ Tuổi Trẻ Muôn Màu](http://phimcoi.net/upload/phim/201607/xem-va-tai-phim-full-hd-Tuoi-Tre-Muon-MauAge-of-Youth-1469858646.jpg)
[Tuổi Trẻ Muôn Màu](phim-bo/tuoi-tre-muon-mau/)12 Tập


markdown:
Phim Cơn Lốc Hận Thù Eun Mi vào vai Hye Bin, một phụ nữ xinh đẹp, giàu có nhưng lại có một cuộc hôn nhân không hạnh phúc. Tham vọng giàu sang, khát khao quyền lực đã khiến Hye Bin đánh đổi mối tình đầu để lựa chọn một ngã rẽ khác. Eun Mi đã thể hiện thành công vai diễn ngoại tình vừa mang tâm lý lo sợ, hoang mang nhưng vẫn chứa đựng sự say mê và cuồng nhiệt khi nắm giữ hạnh phúc trong tay mình. Qua từng cử chỉ, lời nói, ánh mắt, Eun Mi đã xây dựng nên một Hye Bin vừa đáng giận, vừa đáng thương. Tuy nhiên, sâu thẳm trong lòng cô vẫn kiếm tìm hình bóng của người yêu cũ. Sự sắp đặt của số phận đã đưa đẩy Hye Bin gặp gỡ Moo Young (chồng của Jeong Im) và giữa hai người đã nảy sinh tình cảm. Mặc dù biết Moo Young là chồng của bạn thân mình, Hye Bin đã bất chấp tất cả để duy trì mối quan hệ mờ ám.
Bằng mọi thủ đoạn và âm mưu, Hye Bin đã từng bước chia rẽ tình cảm gia đình của Jeong Im. Một Hye Bin luôn ao ước một gia đình hạnh phúc, một mái ấm bình yên nhưng tất cả đã vượt ra khỏi tầm tay khi cô từ bỏ tình yêu để đi theo danh vọng. Khi bí mật bị phát hiện cũng là lúc kịch tính được đẩy lên cao trào. Hye Bin, Jeong Im, Moo Young sẽ đối diện như thế nào với sự thật? Tình yêu chân chính sẽ vượt qua mọi trở ngại hay lòng thù hận sẽ chiến thắng để trừng phạt những kẻ phản bội? Tình yêu như một cơn lốc đến mạnh mẽ nhưng cũng làm người trong cuộc bị tổn thương. Hye Bin có thật sự hạnh phúc với lựa chọn của mình hay cô phải trả giá cho những sai lầm mình gây ra? Câu trả lời sẽ có trong bộ phim Cơn lốc hận thù phát sóng vào lúc 20h hàng ngày trên kênh SCTV13 Phụ nữ và Gia đình.


markdown:
**Xem Phim ** [Đồng Phạm](http://phimviethan.com/phim/dong-pham) - Blood and Ties (2013) [Đồng Phạm](http://phimviethan.com/phim/dong-pham)- Blood and Ties (2013) ** Đồng Phạm** nói về cuộc đời của cô gái Da Eun làm chủ của một cửa hàng kinh doanh nhỏ trong một khu phố, điều đáng nói ở đây là cho đến tận bây giờ cô vẫn không biết được bố của mình làm nghề gì để sinh sống, cho đến một ngày chứng kiến những tên tội phạm đang thực hiện những âm mưu đen tối và nghe nhắc đến tên của bố mình, hoang mang và muốn tìm cho ra sự thật Da Eun dấn thân vào con đường đầy chông gai và nguy hiểm.
Xem phim
[Đồng Phạm Blood and Ties](http://phimviethan.com/phim/dong-pham) trọn bộ, [Phim Đồng Phạm](http://phimviethan.com/phim/dong-pham), Đồng Phạm,Blood and Ties, xem phim Blood and Ties thuyet minh, xem Đồng Phạm vietsub online tap 1, tap 2, tap 3, tap 4, phim Blood and Ties ep 5, ep 6, ep 7, ep 8, ep 9, ep 10, xem Đồng Phạm tập 11, tập 12, tập 13, tập 14, tập 15, phim Đồng Phạm tap 16, tap 17, tap 18, tap 19, tap 20, xem phim Đồng Phạm tập 21, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50...


markdown:
Hiện tại sever lưu trữ phim do lượt xem quá nhiều nên các link up lên đều bị chết link. Mặc dù các uploader đã cố gắng reup liên tục nhưng cũng không khắc phục được hét. Tạm thời BQT đang cố gắng tìm giải pháp để khắc phục. Các bạn hãy comment phim die để bọn mình có thể fix sớm nhất nhé. Cảm ơn các bạn đã ủng hô PhimVIP trong thời gian qua.
Đánh giá phim
Loading...


markdown:
# Du lịch Quảng Bình triển khai nhiệm vụ năm 2014
Chiều ngày 22-01-2014, Sở Văn hóa Thể thao và Du lịch tổ chức hội nghị tổng kết tình hình hoạt động văn hóa, thể thao, du lịch năm 2013 và triển khai nhiệm vụ năm 2014. Dự hội nghị có đồng chí Nguyễn Thị Thanh Hương, Phó Chủ tịch HĐND tỉnh; đồng chí Trần Tiến Dũng, Phó Chủ tịch UBND tỉnh và đại diện các sở, ban, ngành liên quan.
![hoinghi ](http://phongnhaexplorer.com/wp-content/uploads/hoinghi.jpg)
Với phương châm “Đổi mới quản lý, siết chặt kỹ cương, tăng cường hiệu lực, hiệu quả công tác văn hóa, thể thao, du lịch và gia đình”, năm 2013, ngành Văn hóa Thể thao và Du lịch tỉnh đã có nhiều có gắng trong thực hiện nhiệm vụ được giao và đạt nhiều kết quả quan trọng trên các mặt công tác. Toàn ngành cơ bản hoàn thành các mục tiêu, chỉ tiêu kế hoạch theo Nghị quyết của Tỉnh ủy, HĐND, UBND tỉnh và của Bộ Văn Hóa Thể thao và Du lịch. Công tác quản lý Nhà nước tiếp tục được tăng cường, các hoạt động văn hóa, thể thao và du lịch và gia đình sôi nổi, thiết thực phục vụ ngày lễ kỷ niệm, các nhiệm vụ chính trị trọng tâm của địa phương và kỷ niệm 10 năm Vườn Quốc gia Phong Nha – Kẻ Bàng được UNESCO công nhận là Di sản thiên nhiên thế giới.
Trong năm, toàn ngành đã đạt được một số chỉ tiêu cơ bản như: Gia đình đạt danh hiệu Gia đình Văn hóa 162.619/216.429 (chiếm 75%), tăng 0,2%; làng, thôn, bản, khu phố đạt danh hiệu văn hóa 618/1.267, tăng 0,5%; tỷ lệ số người luyện tập thể dục thể thao thường xuyên đạt 28,3%, tăng 0,4% so với cùng kỳ… Thể thao thành tích cao đạt được 229 huy chương, tăng 63 huy chương so với cùng kỳ, vượt kế hoạch giao 101 huy chương. Tổng lượt khách du lịch ước đạt 1,2 triệu lượt khách, tăng 15%, trong đó, khách quốc tế ước 36,7 ngàn lượt, tăng 23% so cùng kỳ, vượt kế hoạch giao 9%, trong đó khách quốc tế vượt 22%. Tổng ngày khách ước đạt trong năm 2013 là 1.387 ngàn ngày, tăng 24%, trong đó ngày khách quốc tế 46,5 ngàn, tăng 35% so cùng kỳ. Doanh thu du lịch ước đạt 1.200 tỷ đồng, tăng 138%, trong đó, doanh thu chuyên ngành du lịch 594,840 tỷ đồng, tăng 70%…
Năm 2014, ngành Văn hóa Thể thao và Du lịch phấn đấu đạt 49% tỷ lệ làng, thôn, bản, khu phố đạt chuẩn văn hóa; 79% tỷ lệ hộ gia đình đạt chuẩn văn hóa; đạt thứ hạng 12 đến 15 trong các tỉnh, thành tham gia Đại hội Thể dục Thể thao toàn quốc. Riêng lĩnh vực du lịch, phấn đấu đạt 1,3 triệu lượt khách, trong đó, khách quốc tế 35 ngàn lượt.
Theo quangbinh.gov.vn
Du lịch Quảng Bình triển khai nhiệm vụ năm 2014
Du lịch Quảng Bình triển khai nhiệm vụ năm 2014 Đạt 5/10 trên 1 đánh giá
## Chia sẻ bài viết
Du lịch Quảng Bình triển khai nhiệm vụ năm 2014

Server: Apache

Cache-Control: private, must-revalidate


markdown:
![ hang6414](http://phunuvn.net/styles/brivium/argon/xenforo/avatars/avatar_male_l.png)
- Hoạt động cuối:
- 11 Tháng mười hai 2013
- Tham gia ngày:
- 2 Tháng mười hai 2013
- Bài viết:
- 0
- Đã được thích:
- 0
- Giới tính:
- Nam
- Sinh nhật:
- 5 Tháng chín 1989 (Age: 27)
### Chia sẻ trang này
# hang6414
Thành viên mới, Nam, 27
- hang6414 được nhìn thấy lần cuối:
- 11 Tháng mười hai 2013
-
- Đang tải...
-
### Giới thiệu
- Giới tính:
- Nam
- Sinh nhật:
- 5 Tháng chín 1989 (Age: 27)
- Hôn nhân:
- Bí mật
### Tương tác


markdown:
# HCM Đại Lý Xe Tải Suzuki Pro 55kg, Mua Bán Xe Tải Suzuki 550kg, Bán Xe Tải Nhẹ Suzuki 550kg!
Thảo luận trong '
[Xe ô tô](forums/xe-o-to.156/)' bắt đầu bởi [xetai](members/xetai.43111/), [13 Tháng tám 2014](threads/dai-ly-xe-tai-suzuki-pro-55kg-mua-ban-xe-tai-suzuki-550kg-ban-xe-tai-nhe-suzuki-550kg.56752/).
0 Trả lời, 323 Đọc
Bài viết tương tự
|Tiêu đề||Date|
|
|
Thảo luận trong '
[Xe ô tô](forums/xe-o-to.156/)' bắt đầu bởi [xetai](members/xetai.43111/), [13 Tháng tám 2014](threads/dai-ly-xe-tai-suzuki-pro-55kg-mua-ban-xe-tai-suzuki-550kg-ban-xe-tai-nhe-suzuki-550kg.56752/).
0 Trả lời, 323 Đọc
|Tiêu đề||Date|
|
|