/* * Copyright 2000 Apple Computer, Inc. * * udfutil.c * - probes for the existence of UDF and return a name */ #include #include #include #include #include #include #include #include #include #include #include #include #include #define fromLE16 NXSwapLittleShortToHost #define fromLE32 NXSwapLittleIntToHost #define FS_TYPE "udf" #define FS_NAME_FILE "UDF" #define DEV_PREFIX "/dev/" #define RAWDEV_PREFIX "/dev/r" #define MOUNT_COMMAND "/sbin/mount" #define UMOUNT_COMMAND "/sbin/umount" #define KEXTLOAD_COMMAND "/sbin/kextload" #define FS_KEXT_DIR "/System/Library/Extensions/udf.kext" #define ISO_BLOCKSIZE 2048 #define VOL_ID_OFFSET 24 #define VOL_ID_LENGTH 32 #define OSTA_COMPRESSED 8 #define OSTA_UNCOMPRESSED 16 /* * Unicode to UTF-8 conversion determines the MAXLABEL size here. To figure out * just what that value is we used the following information: * * Typically one of the decomposition chars is basic ASCII so it takes up 1 * byte in UTF-8. * * The worst case won't happen in practice. For the languages we support * (Tier 1 and Tier 2) the worst cases are shown below. * * The worst case Latin expansion for a single Unicode char would be... * Encoding Size Example * =============================================== * Unicode: 1 char (2 bytes) 0x01D7 * Decomposed: 3 chars (6 bytes) 0x0055 0x0308 0x0301 * UTF-8: 5 bytes (1 + 2 + 2) 0x55 0xCC 0x88 0xCC 0x81 * * The worst case Japanese expansion for a single Unicode char would be... * Unicode: 1 char (2 bytes) 0x30D7 * Decomposed: 2 chars (4 bytes) 0x30D5 0x309A * UTF-8: 6 bytes (3 + 3) 0xDY 0x8Y 0x8Y 0xDY 0x8Y 0x8Y * * The worst case Hanguk expansion for a single Unicode char would be .. * Unicode: 1 char (2 bytes) 0xAC01 * Decomposed: 3 chars (6 bytes) 0x1100 0x1161 0x11A8 * UTF-8: 9 bytes 0xDY 0x8Y 0x8Y 0xDY 0x8Y 0x8Y 0xDY 0x8Y 0x8Y * * So the actual worst case is (# Unicode chars) * 9 * * The volume label on a UDF disk is stored in a 32 byte DString. This leaves * space for 15 unicode characters ((32 - length byte - compression byte)/2). * * So MAXLABEL needs to be 15 * 9 to take care of the worst case possible, * plus one additional byte for the null terminator. * */ #define MAX_LABEL 136 /* globals */ const char *progname; /* our program name, from argv[0] */ int debug; /* use -D to enable debug printfs */ static void usage() { fprintf(stderr, "usage: %s action_arg device_arg [mount_point_arg]\n", progname); fprintf(stderr, "action_arg:\n"); fprintf(stderr, " -%c (Probe)\n", FSUC_PROBE); fprintf(stderr, " -%c (Mount)\n", FSUC_MOUNT); fprintf(stderr, " -%c (Unmount)\n", FSUC_UNMOUNT); fprintf(stderr, "Example:\n"); fprintf(stderr, " %s.util -%c disk2 /mnt\n", FS_TYPE, FSUC_MOUNT); exit(FSUR_INVAL); } int safe_open(char *path, int flags, mode_t mode) { int fd = open(path, flags, mode); if (fd < 0) { fprintf(stderr, "%s: open %s failed, %s\n", progname, path, strerror(errno)); exit(FSUR_IO_FAIL); } return(fd); } void safe_close(int fd) { if (close(fd)) { fprintf(stderr, "%s: close failed, %s\n", progname, strerror(errno)); exit(FSUR_IO_FAIL); } } void safe_write(int fd, char *data, int len) { if (write(fd, data, len) != len) { fprintf(stderr, "%s: write failed, %s\n", progname, strerror(errno)); exit(FSUR_IO_FAIL); } } void safe_execv(char *args[]) { int pid; union wait status; pid = fork(); if (pid == 0) { (void)execv(args[0], args); fprintf(stderr, "%s: execv %s failed, %s\n", progname, args[0], strerror(errno)); exit(FSUR_IO_FAIL); } if (pid == -1) { fprintf(stderr, "%s: fork failed, %s\n", progname, strerror(errno)); exit(FSUR_IO_FAIL); } if (wait4(pid, (int *)&status, 0, NULL) != pid) { fprintf(stderr, "%s: BUG executing %s command\n", progname, args[0]); exit(FSUR_IO_FAIL); } else if (!WIFEXITED(status)) { fprintf(stderr, "%s: %s command aborted by signal %d\n", progname, args[0], WTERMSIG(status)); exit(FSUR_IO_FAIL); } else if (WEXITSTATUS(status)) { fprintf(stderr, "%s: %s command failed, exit status %d: %s\n", progname, args[0], WEXITSTATUS(status), strerror(WEXITSTATUS(status))); exit(FSUR_IO_FAIL); } } void safe_unlink(char *path) { if (unlink(path) && errno != ENOENT) { fprintf(stderr, "%s: unlink %s failed, %s\n", progname, path, strerror(errno)); exit(FSUR_IO_FAIL); } } void safe_read(int fd, char *buf, int nbytes, off_t off) { if (lseek(fd, off, SEEK_SET) == -1) { fprintf(stderr, "%s: device seek error @ %qu, %s\n", progname, off, strerror(errno)); exit(FSUR_IO_FAIL); } if (read(fd, buf, nbytes) != nbytes) { if (debug) fprintf(stderr, "%s: device read error @ %qu, %s\n", progname, off, strerror(errno)); exit(FSUR_IO_FAIL); } } int bsum(unsigned char *s, unsigned len) { /* sum a series of bytes */ int sum = 0; while (len--) sum += *s++; return(sum); } /* * skip over any illegal characters in the string adjusting the name * pointer and length of the string as you go. */ void consumeIllegal( char **name, int * maxLength ) { char *s = *name; int len = *maxLength; while ( len ) { if ((*s != '/') && (*s != '\0')) break; s++; len--; } *name = s; *maxLength = len; } /* * Check to see if the name byte of the DString passed in is correct * Some of the early disks we have seen had a zero in the length field * If we find a zero length, we do a backward crawl to see where the * name actually ends and return that value. */ int validateLength( char *bytes, int fieldLen ) { char *end = &bytes[fieldLen - 1]; int length = *end; if ( length == 0 ) { bytes++; // Skip over the OSTA compression byte while ( end != bytes ) { if (*end != '\0') break; end--; } length = end + 1 - bytes; } else length -= 1; // remove OSTA compression byte from length return length; } int label_get(int fd, unsigned sectorsize, unsigned sector, char *label) { unsigned char *buf = malloc(sectorsize); int found = 0; CFStringRef volumeName; char *defaultName = "Unknown"; char *src, *dst; char theChar; int namelength; if (debug) printf("looking for AVDP @ sector %d\n", sector); if (!buf) { fprintf(stderr, "%s: malloc %d failed\n", progname, sectorsize); exit(FSUR_IO_FAIL); } safe_read(fd, buf, sectorsize, (off_t)(sector * sectorsize)); if (debug) { /* dump out "descriptor tag" */ unsigned char *bp = buf; while (bp < buf+16) printf("%.2x ", *bp++); printf("\n"); } /* * ensure descriptor tag is for AVDP and * verify descriptor tag checksum * per ECMA-167 3/7.2 */ if (fromLE16(*(unsigned short *)buf) == 2 && buf[4] == (bsum(buf, 4) + bsum(buf+5, 11)) % 256) { /* get VDS length (bytes) and location (sector#) */ unsigned len = fromLE32(*(unsigned *)(buf+16)); unsigned loc = fromLE32(*(unsigned *)(buf+20)); if (debug) printf("VDS %d long at sector %d\n", len, loc); for (; len >= sectorsize; len -= sectorsize, loc++) { safe_read(fd, buf, sectorsize, (off_t)(loc * sectorsize)); if (fromLE16(*(unsigned short *)buf) != 1) continue; /* * XXX CSM what about charset? * charset of label is buf[24], and * defined in ECMA-167 1/7.2.1.1 */ if (debug) { /* dump out volume identifier */ unsigned char *bp = buf+24; printf("Volume id, hex (buf[24]-buf[55])\n\t"); while (bp < buf+56) printf("%.2x ", *bp++); printf("\n"); printf("Volume id, interpreted:\n\t"); bp = buf+24; printf("CS:%d STR:", *bp++); while (bp < buf+55) printf("%c", *bp++); printf(" LEN:%d\n", *bp); } /* * The following would just be: * memcpy(label, buf+25, 30) * except that we need to collapse out internal * illegal characters null and slash. They * are collapsed to underscores for consistency * with UDF filename mangling rules for unix. * Note that if we don't do something like this * then autodiskmount's mkdir will fail. * Also note we don't trust the length byte. */ namelength = validateLength( &buf[VOL_ID_OFFSET], VOL_ID_LENGTH ); // Get the number of bytes in Volume Identifier /* * Now check to see if the label is unicode. */ if ( buf[VOL_ID_OFFSET] == OSTA_UNCOMPRESSED ) { volumeName = CFStringCreateWithCharacters(NULL, (UniChar*) &buf[VOL_ID_OFFSET+1], namelength/2 ); if ( volumeName != NULL ) { (void) CFStringGetCString(volumeName, label, MAX_LABEL-1, kCFStringEncodingUTF8); CFRelease(volumeName); namelength = strlen(label); } } else if ( buf[VOL_ID_OFFSET] == OSTA_COMPRESSED ) { memcpy(label, &buf[VOL_ID_OFFSET+1], namelength); label[namelength] = '\0'; } else { namelength = strlen(defaultName); strcpy(label, defaultName); } // Now replace runs of '/' or '\0' with a single '_' src = dst = label; while ( namelength-- > 0 ) { theChar = *src++; if ( (theChar == '/') || (theChar == '\0') ) { consumeIllegal(&src, &namelength); theChar = '_'; } *(dst++) = theChar; } *dst = '\0'; found = 1; break; } } free(buf); return(found); } void safe_ioctl(int fd, unsigned req, char *argp, char *reqstr) { if (ioctl(fd, req, argp) < 0) { fprintf(stderr, "%s: %s failed, %s\n", progname, reqstr, strerror(errno)); exit(FSUR_IO_FAIL); } } int main(int argc, const char *argv[]) { char devpath[MAXPATHLEN]; char opt; struct stat sb; int ret = FSUR_INVAL; /* save & strip off program name */ progname = argv[0]; argc--; argv++; /* secret debug flag - must be 1st flag */ debug = (argc > 0 && !strcmp(argv[0], "-D")); if (debug) { /* strip off debug flag argument */ argc--; argv++; } if (argc < 2 || argv[0][0] != '-') usage(); opt = argv[0][1]; if (opt != FSUC_PROBE && opt != FSUC_MOUNT && opt != FSUC_UNMOUNT) usage(); if ((opt == FSUC_MOUNT || opt == FSUC_UNMOUNT) && argc < 3) usage(); /* mountpoint arg missing! */ sprintf(devpath, "%s%s", RAWDEV_PREFIX, argv[1]); if (stat(devpath, &sb) != 0) { fprintf(stderr, "%s: stat %s failed, %s\n", progname, devpath, strerror(errno)); exit(FSUR_INVAL); } switch (opt) { case FSUC_PROBE: { unsigned char filename[MAXPATHLEN]; int fd, n; unsigned char label[MAX_LABEL]; unsigned char buf[ISO_BLOCKSIZE]; unsigned numsectors, sectorsize; sprintf(filename, "%s/%s%s/%s.label", FS_DIR_LOCATION, FS_TYPE, FS_DIR_SUFFIX, FS_TYPE); safe_unlink(filename); sprintf(filename, "%s/%s%s/%s.name", FS_DIR_LOCATION, FS_TYPE, FS_DIR_SUFFIX, FS_TYPE); safe_unlink(filename); fd = safe_open(devpath, O_RDONLY, 0); /* scan for ISO NSR02 or NSR03 descriptor */ for (n = 16; n < 32; n++) { safe_read(fd, buf, ISO_BLOCKSIZE, (off_t)(n * ISO_BLOCKSIZE)); if (buf[0] == '\0' && !memcmp(&buf[1], "NSR", 3)) break; } if (n >= 32) exit(FSUR_UNRECOGNIZED); if (debug) printf("found NSR @ sector %d\n", n); safe_ioctl(fd, DKIOCBLKSIZE, (char *)§orsize, "DKIOCBLKSIZE"); safe_ioctl(fd, DKIOCNUMBLKS, (char *)&numsectors, "DKIOCNUMBLKS"); if (debug) printf("sectorsize = %d, numsectors = %d\n", sectorsize, numsectors); /* We try the native values first, if that fails we assume the */ /* volume was created as a 2K logical sector size on a device */ /* that had a sector size other than 2K and we retry. */ if (!label_get(fd, sectorsize, 256, label) && !label_get(fd, sectorsize, numsectors-256, label) && !label_get(fd, sectorsize, numsectors-1, label)) { fprintf(stderr, "%s: no volume descriptor found using native %d byte sector size.\n", progname, sectorsize); if (sectorsize != ISO_BLOCKSIZE) { fprintf(stderr, "%s: trying %d logical sector size.\n", progname, ISO_BLOCKSIZE); numsectors = ((off_t)(sectorsize * numsectors)) / ISO_BLOCKSIZE; sectorsize = ISO_BLOCKSIZE; if (!label_get(fd, sectorsize, 256, label) && !label_get(fd, sectorsize, numsectors-256, label) && !label_get(fd, sectorsize, numsectors-1, label)) { fprintf (stderr, "%s: no volume descriptor found using simulated sector size!\n", progname); exit(FSUR_UNRECOGNIZED); } } else exit(FSUR_UNRECOGNIZED); } safe_close(fd); /* write the .label file */ sprintf(filename, "%s/%s%s/%s.label", FS_DIR_LOCATION, FS_TYPE, FS_DIR_SUFFIX, FS_TYPE); fd = safe_open(filename, O_WRONLY|O_CREAT|O_EXCL, 0755); safe_write(fd, label, strlen(label) + 1); safe_close(fd); /* write the .name file */ sprintf(filename, "%s/%s%s/%s.name", FS_DIR_LOCATION, FS_TYPE, FS_DIR_SUFFIX, FS_TYPE); fd = safe_open(filename, O_WRONLY|O_CREAT|O_EXCL, 0755); safe_write(fd, FS_NAME_FILE, 1 + strlen(FS_NAME_FILE)); safe_close(fd); ret = FSUR_RECOGNIZED; break; } case FSUC_MOUNT: { const char *kextargs[] = {KEXTLOAD_COMMAND, FS_KEXT_DIR, NULL}; const char *mountargs[11]; mountargs[0] = MOUNT_COMMAND; mountargs[1] = "-t"; mountargs[2] = FS_TYPE; mountargs[3] = "-r"; if (argc >= 4 && !strcmp(argv[4], DEVICE_WRITABLE)) mountargs[3] = "-w"; // UDF should not be mounted setuid mountargs[4] = "-o"; mountargs[5] = "nosuid"; // UDF should not be mounted dev mountargs[6] = "-o"; mountargs[7] = "nodev"; mountargs[8] = devpath; mountargs[9] = argv[2]; mountargs[10] = NULL; sprintf(devpath, "%s%s", DEV_PREFIX, argv[1]); safe_execv(kextargs); /* better here than in mount_udf */ safe_execv(mountargs); ret = FSUR_IO_SUCCESS; break; } case FSUC_UNMOUNT: { const char *umountargs[] = {UMOUNT_COMMAND, argv[2], NULL}; safe_execv(umountargs); ret = FSUR_IO_SUCCESS; break; } default: /* argument validation should preclude getting here */ exit(FSUR_INVAL); break; } exit(ret); return(ret); }