/* * Copyright (c) 2001-2002 Apple Computer, Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The 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, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ /* * nbimages.c * - NetBoot image list routines */ #include #include #include #include #include #include #include #include #include #include #include #include #include "dynarray.h" #include "nbimages.h" #include "cfutil.h" #include "util.h" #include "NetBootServer.h" #include "NetBootImageInfo.h" #include #include #include #include struct NBImageList_s { dynarray_t list; }; extern void my_log(int priority, const char *message, ...); static int cfstring_to_cstring(CFStringRef cfstr, char * str, int len) { if (CFStringGetCString(cfstr, str, len, kCFStringEncodingUTF8)) { return (TRUE); } *str = '\0'; return (FALSE); } /* * Function: find_colon * Purpose: * Find the next unescaped instance of the colon character. */ static __inline__ char * find_colon(char * str) { char * start = str; char * colon; while ((colon = strchr(start, ':')) != NULL) { if (colon == start) { break; } if (colon[-1] != '\\') break; start = colon; } return (colon); } /* * Function: parse_nfs_path * Purpose: * Parse a string of the form: * ":[:]" * into the given ip address, mount point, and optionally, image_path. * Notes: * - the passed in string is modified i.e. ':' is replaced by '\0' * - literal colons must be escaped with a backslash * * Examples: * 17.202.42.112:/Library/NetBoot/NetBootSP0:Jaguar/Jaguar.dmg * siegdi6:/Volumes/Foo\:/Library/NetBoot/NetBootSP0:Jaguar/Jaguar.dmg */ static __inline__ boolean_t parse_nfs_path(char * path, struct in_addr * iaddr_p, char * * mount_dir, char * * image_path) { char * start; char * colon; /* IP address */ start = path; colon = strchr(start, ':'); if (colon == NULL) { return (FALSE); } *colon = '\0'; if (inet_aton(start, iaddr_p) != 1) { struct in_addr * * addr; struct hostent * ent; ent = gethostbyname(start); if (ent == NULL) { return (FALSE); } addr = (struct in_addr * *)ent->h_addr_list; if (*addr == NULL) return (FALSE); *iaddr_p = **addr; } /* mount point */ start = colon + 1; colon = find_colon(start); *mount_dir = start; if (colon == NULL) { *image_path = NULL; } else { /* image path */ *colon = '\0'; start = colon + 1; (void)find_colon(start); *image_path = start; } return (TRUE); } /* * Function: parse_http_path * Purpose: * Parse a string of the form: * "http://[user:password@][:port]/" * into the given ip address, image_path, and option user/password and port * Notes: * - the passed in string is modified i.e. ':' is replaced by '\0' * * Examples: * http://17.203.12.194:8080/NetBootSP0/Jaguar/Jaguar.dmg * http://foo:bar@17.203.12.194/NetBootSP0/Jaguar/Jaguar.dmg */ static __inline__ boolean_t parse_http_path(char * path, struct in_addr * iaddr_p, char * * username, char * * password, int * port, char * * image_path) { char * atchar; char * colon; char * slash; char * start; *username = NULL; *password = NULL; *port = 0; #define HTTP_URL_PREFIX "http://" #define HTTP_URL_PREFIX_LEN 7 /* scheme */ start = path; if (strncmp(HTTP_URL_PREFIX, start, HTTP_URL_PREFIX_LEN) != 0) { return (FALSE); } start += HTTP_URL_PREFIX_LEN; /* look for start of image path */ slash = strchr(start, '/'); if (slash == NULL) { return (FALSE); } *slash = '\0'; *image_path = slash + 1; /* check for optional username:password@... */ atchar = strchr(start, '@'); if (atchar != NULL && atchar < slash) { *atchar = '\0'; *username = start; *password = strsep(username, ":"); if (*password == NULL) { /* both username and password need to specified */ return (FALSE); } start = atchar + 1; } /* check for optional port in server_name_or_ip[:port] */ colon = strchr(start, ':'); if (colon) { *colon = '\0'; *port = atoi(colon + 1); } /* if the server specification isn't an IP address, look it up by name */ if (inet_aton(start, iaddr_p) != 1) { struct in_addr * * addr; struct hostent * ent; ent = gethostbyname(start); if (ent == NULL) { return (FALSE); } addr = (struct in_addr * *)ent->h_addr_list; if (*addr == NULL) return (FALSE); *iaddr_p = **addr; } return (TRUE); } int NBImageList_count(NBImageListRef image_list) { dynarray_t * dlist = &image_list->list; return (dynarray_count(dlist)); } NBImageEntryRef NBImageList_element(NBImageListRef image_list, int i) { dynarray_t * dlist = &image_list->list; return (dynarray_element(dlist, i)); } NBImageEntryRef NBImageList_elementWithID(NBImageListRef image_list, bsdp_image_id_t image_id) { dynarray_t * dlist = &image_list->list; int i; int count; count = dynarray_count(dlist); for (i = 0; i < count; i++) { NBImageEntryRef entry = dynarray_element(dlist, i); if (image_id == entry->image_id) { return (entry); } } return (NULL); } void NBImageList_free(NBImageListRef * l) { NBImageListRef image_list; if (l == NULL) { return; } image_list = *l; if (image_list == NULL) { return; } dynarray_free(&image_list->list); free(image_list); *l = NULL; return; } static boolean_t stat_file(const char * dir, const char * file) { char path[PATH_MAX]; struct stat sb; snprintf(path, sizeof(path), "%s/%s", dir, file); if (stat(path, &sb) < 0) { #if 0 fprintf(stderr, "stat %s failed, %s\n", path, strerror(errno)); #endif 0 return (FALSE); } if ((sb.st_mode & S_IFREG) == 0) { #if 0 fprintf(stderr, "%s is not a file\n", path); #endif 0 return (FALSE); } return (TRUE); } static const char * NBImageTypeStr(NBImageType type) { switch (type) { case kNBImageTypeClassic: return "Classic"; case kNBImageTypeNFS: return "NFS"; case kNBImageTypeHTTP: return "HTTP"; case kNBImageTypeBootFileOnly: return "BootFile"; default: return ""; } } static void dump_strlist(const char * * strlist, int count) { int i; for (i = 0; i < count; i++) { printf("%s%s", (i != 0) ? ", " : "", strlist[i]); } return; } static void dump_ether_list(const struct ether_addr * list, int count) { int i; for (i = 0; i < count; i++) { printf("%s%s", (i != 0) ? ", " : "", ether_ntoa(list + i)); } return; } static void NBImageEntry_print(NBImageEntryRef entry) { int i; printf("%-12s %-35s %-35s 0x%08x %-9s %-12s", entry->sharepoint->name, entry->dir_name_esc, entry->name, entry->image_id, NBImageTypeStr(entry->type), entry->bootfile); switch (entry->type) { case kNBImageTypeClassic: printf(" %-12s", entry->type_info.classic.shared); if (entry->type_info.classic.private != NULL) { printf(" %-12s", entry->type_info.classic.private); } break; case kNBImageTypeNFS: printf(" %-12s%s", entry->type_info.nfs.root_path, (entry->type_info.nfs.indirect == TRUE)? " [indirect]" : ""); break; case kNBImageTypeHTTP: printf(" %-12s%s", entry->type_info.http.root_path_esc, (entry->type_info.http.indirect == TRUE)? " [indirect]" : ""); break; default: break; } printf(" ["); for (i = 0; i < entry->archlist_count; i++) { printf("%s%s", (i > 0) ? ", " : "", entry->archlist[i]); } printf("]"); if (entry->sysids != NULL) { printf(" [ "); dump_strlist(entry->sysids, entry->sysids_count); printf(" ]"); } if (entry->disabled_mac_addresses != NULL) { printf(" -[ "); dump_ether_list(entry->disabled_mac_addresses, entry->disabled_mac_addresses_count); printf(" ]-"); } if (entry->enabled_mac_addresses != NULL) { printf(" +[ "); dump_ether_list(entry->enabled_mac_addresses, entry->enabled_mac_addresses_count); printf(" ]+"); } if (entry->filter_only) { printf(" "); } if (entry->diskless) { printf(" "); } printf("\n"); return; } static int escape_path(const char * path, int path_len, char * escaped_path, int escaped_path_len) { CFDataRef data = NULL; int data_len; int len = 0; CFURLRef url = NULL; url = CFURLCreateFromFileSystemRepresentation(NULL, (const UInt8 *)path, path_len, FALSE); if (url == NULL) { goto done; } data = CFURLCreateData(NULL, url, kCFStringEncodingUTF8, TRUE); if (data == NULL) { goto done; } data_len = CFDataGetLength(data); if (data_len >= escaped_path_len) { /* would truncate, so don't bother */ goto done; } if (data_len == path_len && bcmp(CFDataGetBytePtr(data), path, path_len) == 0) { /* exactly the same string, no need to escape */ goto done; } bcopy(CFDataGetBytePtr(data), escaped_path, data_len); escaped_path[data_len] = '\0'; len = data_len; done: if (url != NULL) { CFRelease(url); } return (len); } static boolean_t S_get_plist_boolean(CFDictionaryRef plist, CFStringRef key, boolean_t d) { CFBooleanRef b; boolean_t ret = d; b = CFDictionaryGetValue(plist, key); if (isA_CFBoolean(b) != NULL) { ret = CFBooleanGetValue(b); } return (ret); } static int my_ptrstrcmp(const void * v1, const void * v2) { const char * * s1 = (const char * *)v1; const char * * s2 = (const char * *)v2; return (strcmp(*s1, *s2)); } static struct in_addr cfhost_to_ip(CFStringRef host) { struct in_addr iaddr = { 0 }; char tmp[PATH_MAX]; if (isA_CFString(host) == NULL) { goto done; } if (cfstring_to_cstring(host, tmp, sizeof(tmp)) == FALSE) { goto done; } /* if the server specification isn't an IP address, look it up by name */ if (inet_aton(tmp, &iaddr) != 1) { struct in_addr * * addr; struct hostent * ent; ent = gethostbyname(tmp); if (ent == NULL) { goto done; } addr = (struct in_addr * *)ent->h_addr_list; if (*addr == NULL) { goto done; } iaddr = **addr; } done: return (iaddr); } typedef int (*qsort_compare_func_t)(const void *, const void *); static NBImageEntryRef NBImageEntry_create(NBSPEntryRef sharepoint, char * dir_name, char * dir_path, char * info_plist_path, boolean_t allow_diskless) { CFArrayRef archlist_prop; int archlist_space = 0; u_int16_t attr = 0; CFStringRef bootfile_prop; int bootfile_space = 0; char dir_name_esc[PATH_MAX]; int dir_name_esc_len; int dir_name_len; CFArrayRef disabled_mac_prop = NULL; int disabled_mac_space = 0; boolean_t diskless; NBImageEntryRef entry = NULL; CFArrayRef enabled_mac_prop = NULL; int enabled_mac_space = 0; boolean_t filter_only; int i; int32_t idx_val = -1; CFNumberRef idx; char * image_file = NULL; boolean_t image_is_default; boolean_t indirect = FALSE; CFNumberRef kind; int32_t kind_val = -1; char * mount_point = NULL; CFStringRef name_prop; int name_space; char * offset; CFPropertyListRef plist; CFStringRef private_prop = NULL; int private_space = 0; char root_path[PATH_MAX]; int root_path_len = 0; char root_path_esc[PATH_MAX]; int root_path_esc_len = 0; CFStringRef root_path_prop = NULL; struct in_addr server_ip; char * server_password; int server_port; char * server_username; CFStringRef shared_prop = NULL; int shared_space = 0; int tail_space = 0; int sysids_space = 0; CFArrayRef sysids_prop; char tmp[PATH_MAX]; CFStringRef type; NBImageType type_val = kNBImageTypeNone; /* space for directory name */ dir_name_len = strlen(dir_name); tail_space += dir_name_len + 1; plist = my_CFPropertyListCreateFromFile(info_plist_path); if (isA_CFDictionary(plist) == NULL) { goto failed; } if (S_get_plist_boolean(plist, kNetBootImageInfoIsEnabled, TRUE) == FALSE) { /* image is disabled */ goto failed; } if (S_get_plist_boolean(plist, kNetBootImageInfoIsInstall, FALSE) == TRUE) { attr |= BSDP_IMAGE_ATTRIBUTES_INSTALL; } image_is_default = S_get_plist_boolean(plist, kNetBootImageInfoIsDefault, FALSE); diskless = S_get_plist_boolean(plist, kNetBootImageInfoSupportsDiskless, FALSE); if (allow_diskless == FALSE) { /* ignore diskless setting if not allowed */ diskless = FALSE; } filter_only = S_get_plist_boolean(plist, kNetBootImageInfoFilterOnly, FALSE); name_prop = CFDictionaryGetValue(plist, kNetBootImageInfoName); if (isA_CFString(name_prop) == NULL) { fprintf(stderr, "missing/invalid Name property\n"); goto failed; } name_space = my_CFStringToCStringAndLengthExt(name_prop, NULL, 0, TRUE); if (name_space <= 1) { printf("empty Name property\n"); goto failed; } tail_space += name_space; idx = CFDictionaryGetValue(plist, kNetBootImageInfoIndex); if (isA_CFNumber(idx) == NULL || CFNumberGetValue(idx, kCFNumberSInt32Type, &idx_val) == FALSE || idx_val <= 0 || idx_val > BSDP_IMAGE_INDEX_MAX) { fprintf(stderr, "missing/invalid Index property\n"); goto failed; } kind = CFDictionaryGetValue(plist, kNetBootImageInfoKind); if (isA_CFNumber(kind) != NULL) { if (CFNumberGetValue(kind, kCFNumberSInt32Type, &kind_val) == FALSE || kind_val < 0 || kind_val > BSDP_IMAGE_ATTRIBUTES_KIND_MAX) { kind_val = -1; } } type = CFDictionaryGetValue(plist, kNetBootImageInfoType); if (isA_CFString(type) == NULL) { fprintf(stderr, "missing/invalid Type property\n"); goto failed; } if (CFEqual(type, kNetBootImageInfoTypeClassic)) { if (allow_diskless == FALSE) { fprintf(stderr, "ignoring Classic image: diskless resources unavailable\n"); goto failed; } type_val = kNBImageTypeClassic; if (kind_val == -1) { kind_val = bsdp_image_kind_MacOS9; } diskless = TRUE; /* Mac OS 9 requires diskless */ } else if (CFEqual(type, kNetBootImageInfoTypeNFS)) { type_val = kNBImageTypeNFS; if (kind_val == -1) { kind_val = bsdp_image_kind_MacOSX; } } else if (CFEqual(type, kNetBootImageInfoTypeHTTP)) { type_val = kNBImageTypeHTTP; if (kind_val == -1) { kind_val = bsdp_image_kind_MacOSX; } } else if (CFEqual(type, kNetBootImageInfoTypeBootFileOnly)) { type_val = kNBImageTypeBootFileOnly; diskless = FALSE; } if (type_val == kNBImageTypeNone) { fprintf(stderr, "unrecognized Type property\n"); goto failed; } if (kind_val == -1) { fprintf(stderr, "missing/unrecognized Kind value\n"); goto failed; } if (kind_val == bsdp_image_kind_Diagnostics) { /* if FilterOnly was not set, set it to TRUE */ if (CFDictionaryContainsKey(plist, kNetBootImageInfoFilterOnly) == FALSE) { filter_only = TRUE; } } attr |= bsdp_image_attributes_from_kind(kind_val); /* space for escaped directory name */ if (type_val == kNBImageTypeHTTP) { dir_name_esc_len = escape_path(dir_name, dir_name_len, dir_name_esc, sizeof(dir_name_esc)); if (dir_name_esc_len != 0) { tail_space += dir_name_esc_len + 1; } } else { dir_name_esc_len = 0; } /* architectures */ archlist_prop = CFDictionaryGetValue(plist, kNetBootImageInfoArchitectures); if (archlist_prop != NULL) { int archlist_count; if (my_CFStringArrayToCStringArray(archlist_prop, NULL, &archlist_space, &archlist_count) == FALSE) { fprintf(stderr, "Couldn't calculate Archlist length\n"); goto failed; } if (archlist_count == 0) { fprintf(stderr, "Empty ArchList array"); goto failed; } tail_space += archlist_space; } /* bootfile */ bootfile_prop = CFDictionaryGetValue(plist, kNetBootImageInfoBootFile); if (bootfile_prop != NULL && isA_CFString(bootfile_prop) == NULL) { fprintf(stderr, "invalid BootFile property\n"); goto failed; } if (bootfile_prop == NULL) { fprintf(stderr, "no BootFile property specified\n"); goto failed; } bootfile_space = my_CFStringToCStringAndLength(bootfile_prop, NULL, 0); tail_space += bootfile_space; /* supported system ids */ sysids_prop = CFDictionaryGetValue(plist, kNetBootImageInfoEnabledSystemIdentifiers); if (sysids_prop != NULL) { int sysids_count; if (isA_CFArray(sysids_prop) == NULL) { fprintf(stderr, "EnabledSystemIdentifiers isn't an array\n"); goto failed; } if (my_CFStringArrayToCStringArray(sysids_prop, NULL, &sysids_space, &sysids_count) == FALSE) { fprintf(stderr, "Couldn't calculate EnabledSystemIdentifiers length\n"); goto failed; } if (sysids_count == 0) { /* if the list is empty, treat it as if it were not there at all */ sysids_prop = NULL; } else { tail_space += sysids_space; } } /* enabled MAC addresses */ enabled_mac_prop = CFDictionaryGetValue(plist, kNetBootImageInfoEnabledMACAddresses); if (enabled_mac_prop != NULL) { int enabled_mac_count; if (isA_CFArray(enabled_mac_prop) == NULL) { fprintf(stderr, "EnabledMACAddresses isn't an array\n"); goto failed; } if (my_CFStringArrayToEtherArray(enabled_mac_prop, NULL, &enabled_mac_space, &enabled_mac_count) == FALSE) { fprintf(stderr, "Couldn't calculate EnabledMACAddresses length\n"); goto failed; } if (enabled_mac_count == 0) { enabled_mac_prop = NULL; } else { tail_space += enabled_mac_space; } } /* disabled MAC addresses */ disabled_mac_prop = CFDictionaryGetValue(plist, kNetBootImageInfoDisabledMACAddresses); if (disabled_mac_prop != NULL) { int disabled_mac_count; if (isA_CFArray(disabled_mac_prop) == NULL) { fprintf(stderr, "DisabledMACAddresses isn't an array\n"); goto failed; } if (my_CFStringArrayToEtherArray(disabled_mac_prop, NULL, &disabled_mac_space, &disabled_mac_count) == FALSE) { fprintf(stderr, "Couldn't calculate DisabledMACAddresses length\n"); goto failed; } if (disabled_mac_count == 0) { disabled_mac_prop = NULL; } else { tail_space += disabled_mac_space; } } switch (type_val) { case kNBImageTypeClassic: /* must have Shared */ shared_prop = CFDictionaryGetValue(plist, kNetBootImageInfoSharedImage); if (isA_CFString(shared_prop) == NULL) { fprintf(stderr, "missing/invalid SharedImage property\n"); goto failed; } shared_space = my_CFStringToCStringAndLength(shared_prop, tmp, sizeof(tmp)); if (stat_file(dir_path, tmp) == FALSE) { fprintf(stderr, "SharedImage does not exist\n"); goto failed; } tail_space += shared_space; /* may have Private */ private_prop = isA_CFString(CFDictionaryGetValue(plist, kNetBootImageInfoPrivateImage)); if (private_prop != NULL) { private_space = my_CFStringToCStringAndLength(private_prop, tmp, sizeof(tmp)); if (stat_file(dir_path, tmp)) { tail_space += private_space; } else { private_prop = NULL; } } break; case kNBImageTypeNFS: /* must have RootPath */ root_path_prop = CFDictionaryGetValue(plist, kNetBootImageInfoRootPath); if (isA_CFString(root_path_prop) == NULL) { fprintf(stderr, "missing/invalid RootPath property\n"); goto failed; } if (cfstring_to_cstring(root_path_prop, tmp, sizeof(tmp)) == FALSE) { fprintf(stderr, "RootPath could not be converted\n"); goto failed; } if (stat_file(dir_path, tmp) == TRUE) { strlcpy(root_path, tmp, sizeof(root_path)); } else if (parse_nfs_path(tmp, &server_ip, &mount_point, &image_file) == TRUE) { if (image_file) { snprintf(root_path, sizeof(root_path), "nfs:%s:%s:%s", inet_ntoa(server_ip), mount_point, image_file); } else { snprintf(root_path, sizeof(root_path), "nfs:%s:%s", inet_ntoa(server_ip), mount_point); } indirect = TRUE; } else { goto failed; } root_path_len = strlen(root_path); tail_space += root_path_len + 1; break; case kNBImageTypeHTTP: /* must have RootPath */ root_path_prop = CFDictionaryGetValue(plist, kNetBootImageInfoRootPath); if (isA_CFString(root_path_prop) == NULL) { fprintf(stderr, "missing/invalid RootPath property\n"); goto failed; } if (cfstring_to_cstring(root_path_prop, tmp, sizeof(tmp)) == FALSE) { fprintf(stderr, "RootPath could not be converted\n"); goto failed; } if (stat_file(dir_path, tmp) == TRUE) { strlcpy(root_path, tmp, sizeof(root_path)); root_path_len = strlen(root_path); root_path_esc_len = escape_path(root_path, root_path_len, root_path_esc, sizeof(root_path_esc)); } else if (parse_http_path(tmp, &server_ip, &server_username, &server_password, &server_port, &image_file) == TRUE) { if (server_username && server_password) { if (server_port != 0) { snprintf(root_path, sizeof(root_path), "http://%s:%s@%s:%d/%s", server_username, server_password, inet_ntoa(server_ip), server_port, image_file); } else { snprintf(root_path, sizeof(root_path), "http://%s:%s@%s/%s", server_username, server_password, inet_ntoa(server_ip), image_file); } } else { if (server_port != 0) { snprintf(root_path, sizeof(root_path), "http://%s:%d/%s", inet_ntoa(server_ip), server_port, image_file); } else { snprintf(root_path, sizeof(root_path), "http://%s/%s", inet_ntoa(server_ip), image_file); } } root_path_len = strlen(root_path); indirect = TRUE; } else { goto failed; } tail_space += root_path_len + 1; if (root_path_esc_len != 0) { tail_space += root_path_esc_len + 1; } break; case kNBImageTypeBootFileOnly: default: break; } entry = (NBImageEntryRef)malloc(sizeof(*entry) + tail_space); if (entry == NULL) { goto failed; } bzero(entry, sizeof(*entry)); entry->image_id = bsdp_image_id_make(idx_val, attr); entry->type = type_val; entry->is_default = image_is_default; entry->diskless = diskless; entry->filter_only = filter_only; entry->load_balance_ip = cfhost_to_ip(CFDictionaryGetValue(plist, kNetBootImageLoadBalanceServer)); offset = (char *)(entry + 1); /* do pointer arrays + strings first */ /* archlist */ if (archlist_prop != NULL) { entry->archlist = (const char * *)offset; (void)my_CFStringArrayToCStringArray(archlist_prop, offset, &archlist_space, &entry->archlist_count); offset += archlist_space; } else { entry->arch = "ppc"; entry->archlist = &entry->arch; entry->archlist_count = 1; } /* supported system ids */ if (sysids_prop != NULL) { entry->sysids = (const char * *)offset; (void)my_CFStringArrayToCStringArray(sysids_prop, offset, &sysids_space, &entry->sysids_count); qsort(entry->sysids, entry->sysids_count, sizeof(char *), my_ptrstrcmp); offset += sysids_space; } /* enabled MAC addresses */ if (enabled_mac_prop != NULL) { entry->enabled_mac_addresses = (const struct ether_addr *)offset; (void)my_CFStringArrayToEtherArray(enabled_mac_prop, offset, &enabled_mac_space, &entry->enabled_mac_addresses_count); qsort((void *)entry->enabled_mac_addresses, entry->enabled_mac_addresses_count, sizeof(*entry->enabled_mac_addresses), (qsort_compare_func_t)ether_cmp); offset += enabled_mac_space; } /* disabled MAC addresses */ if (disabled_mac_prop != NULL) { entry->disabled_mac_addresses = (const struct ether_addr *)offset; (void)my_CFStringArrayToEtherArray(disabled_mac_prop, offset, &disabled_mac_space, &entry->disabled_mac_addresses_count); qsort((void *)entry->disabled_mac_addresses, entry->disabled_mac_addresses_count, sizeof(*entry->disabled_mac_addresses), (qsort_compare_func_t)ether_cmp); offset += disabled_mac_space; } /* ... then just strings */ /* bootfile */ entry->bootfile = offset; (void)my_CFStringToCStringAndLength(bootfile_prop, offset, bootfile_space); /* verify that bootfile exists for every defined architecture */ for (i = 0; i < entry->archlist_count; i++) { char path[PATH_MAX]; snprintf(path, sizeof(path), "%s/%s", entry->archlist[i], entry->bootfile); if (stat_file(dir_path, path) == FALSE) { if (strcmp(entry->archlist[i], "ppc") == 0 && stat_file(dir_path, entry->bootfile)) { entry->ppc_bootfile_no_subdir = TRUE; } else { fprintf(stderr, "BootFile does not exist\n"); goto failed; } } } offset += bootfile_space; /* sharepoint */ entry->sharepoint = sharepoint; /* dir_name */ entry->dir_name = offset; strlcpy(entry->dir_name, dir_name, dir_name_len + 1); offset += dir_name_len + 1; /* dir_name_esc */ if (dir_name_esc_len == 0) { entry->dir_name_esc = entry->dir_name; } else { entry->dir_name_esc = offset; strlcpy(entry->dir_name_esc, dir_name_esc, dir_name_esc_len + 1); offset += dir_name_esc_len + 1; } /* name */ entry->name = offset; (void)my_CFStringToCStringAndLengthExt(name_prop, offset, name_space, TRUE); entry->name_length = name_space - 1; offset += name_space; switch (type_val) { case kNBImageTypeClassic: entry->type_info.classic.shared = offset; (void)my_CFStringToCStringAndLength(shared_prop, offset, shared_space); offset += shared_space; if (private_prop != NULL) { entry->type_info.classic.private = offset; (void)my_CFStringToCStringAndLength(private_prop, offset, private_space); offset += private_space; } break; case kNBImageTypeNFS: entry->type_info.nfs.root_path = offset; strcpy((char *)entry->type_info.nfs.root_path, root_path); offset += root_path_len + 1; entry->type_info.nfs.indirect = indirect; break; case kNBImageTypeHTTP: entry->type_info.http.root_path = offset; strcpy((char *)entry->type_info.http.root_path, root_path); offset += root_path_len + 1; if (root_path_esc_len == 0) { entry->type_info.http.root_path_esc = entry->type_info.http.root_path; } else { entry->type_info.http.root_path_esc = offset; strcpy((char *)entry->type_info.http.root_path_esc, root_path_esc); offset += root_path_esc_len + 1; } entry->type_info.http.indirect = indirect; break; default: break; } #if 0 printf("tail_space %d - actual %d = %d\n", tail_space, (offset - (char *)(entry + 1)), tail_space - (offset - (char *)(entry + 1))); #endif my_CFRelease(&plist); return (entry); failed: if (entry != NULL) { free(entry); entry = NULL; } my_CFRelease(&plist); return (entry); } boolean_t NBImageEntry_supported_sysid(NBImageEntryRef entry, const char * arch, const char * sysid, const struct ether_addr * ether) { boolean_t found = FALSE; int i; for (i = 0; i < entry->archlist_count; i++) { if (strcmp(entry->archlist[i], arch) == 0) { found = TRUE; break; } } if (found == FALSE) { /* not a supported architecture */ return (FALSE); } if (entry->sysids != NULL) { if (bsearch(&sysid, entry->sysids, entry->sysids_count, sizeof(char *), my_ptrstrcmp) == NULL) { /* not a supported system identifier */ return (FALSE); } } if (entry->disabled_mac_addresses != NULL) { if (bsearch(ether, entry->disabled_mac_addresses, entry->disabled_mac_addresses_count, sizeof(*ether), (qsort_compare_func_t)ether_cmp) != NULL) { /* ethernet address explicitly disabled */ return (FALSE); } } if (entry->enabled_mac_addresses != NULL) { if (bsearch(ether, entry->enabled_mac_addresses, entry->enabled_mac_addresses_count, sizeof(*ether), (qsort_compare_func_t)ether_cmp) == NULL) { /* ethernet address not explicitly enabled */ return (FALSE); } } return (TRUE); } boolean_t NBImageEntry_attributes_match(NBImageEntryRef entry, const u_int16_t * attrs_list, int n_attrs_list) { u_int16_t attrs; int i; if (attrs_list == NULL) { return (!entry->filter_only); } attrs = bsdp_image_attributes(entry->image_id); for (i = 0; i < n_attrs_list; i++) { if (attrs_list[i] == attrs) { return (TRUE); } } return (FALSE); } static void NBImageList_add_default_entry(NBImageListRef image_list, NBImageEntryRef entry) { dynarray_t * dlist = &image_list->list; int i; int count; if (entry->sysids == NULL) { dynarray_insert(dlist, entry, 0); return; } count = dynarray_count(dlist); for (i = 0; i < count; i++) { NBImageEntryRef scan = dynarray_element(dlist, i); if (scan->is_default == FALSE || scan->sysids != NULL) { dynarray_insert(dlist, entry, i); return; } } dynarray_add(dlist, entry); return; } static void NBImageList_add_entry(NBImageListRef image_list, NBImageEntryRef entry) { NBImageEntryRef scan; scan = NBImageList_elementWithID(image_list, entry->image_id); if (scan != NULL) { fprintf(stderr, "Ignoring image with non-unique image index %d:\n", bsdp_image_index(entry->image_id)); NBImageEntry_print(entry); free(entry); return; } if (entry->is_default) { NBImageList_add_default_entry(image_list, entry); } else { dynarray_add(&image_list->list, entry); } return; } static void NBImageList_add_images(NBImageListRef image_list, NBSPEntryRef sharepoint, boolean_t allow_diskless) { char dir[PATH_MAX]; DIR * dir_p; NBImageEntryRef entry; char info_path[PATH_MAX]; int suffix_len; struct dirent * scan; struct stat sb; dir_p = opendir(sharepoint->path); if (dir_p == NULL) { goto done; } suffix_len = strlen(NETBOOT_IMAGE_SUFFIX); while ((scan = readdir(dir_p)) != NULL) { int entry_len = strlen(scan->d_name); if (entry_len < suffix_len || strcmp(scan->d_name + entry_len - suffix_len, NETBOOT_IMAGE_SUFFIX) != 0) { continue; } snprintf(dir, sizeof(dir), "%s/%s", sharepoint->path, scan->d_name); if (stat(dir, &sb) != 0 || (sb.st_mode & S_IFDIR) == 0) { continue; } snprintf(info_path, sizeof(info_path), "%s/" NETBOOT_IMAGE_INFO_PLIST, dir); if (stat(info_path, &sb) != 0 || (sb.st_mode & S_IFREG) == 0) { continue; } entry = NBImageEntry_create(sharepoint, scan->d_name, dir, info_path, allow_diskless); if (entry != NULL) { NBImageList_add_entry(image_list, entry); } } done: if (dir_p) closedir(dir_p); return; } NBImageEntryRef NBImageList_default(NBImageListRef image_list, const char * arch, const char * sysid, const struct ether_addr * ether, const u_int16_t * attrs, int n_attrs) { int count; dynarray_t * dlist = &image_list->list; int i; count = dynarray_count(dlist); for (i = 0; i < count; i++) { NBImageEntryRef scan = dynarray_element(dlist, i); if (NBImageEntry_supported_sysid(scan, arch, sysid, ether) && NBImageEntry_attributes_match(scan, attrs, n_attrs)) { return (scan); } } return (NULL); } NBImageListRef NBImageList_init(NBSPListRef sharepoints, boolean_t allow_diskless) { int count; int i; NBImageListRef image_list = NULL; image_list = (NBImageListRef)malloc(sizeof(*image_list)); if (image_list == NULL) { goto done; } bzero(image_list, sizeof(*image_list)); dynarray_init(&image_list->list, free, NULL); count = NBSPList_count(sharepoints); for (i = 0; i < count; i++) { NBSPEntryRef entry = NBSPList_element(sharepoints, i); NBImageList_add_images(image_list, entry, allow_diskless); } done: if (image_list != NULL) { if (dynarray_count(&image_list->list) == 0) { dynarray_free(&image_list->list); free(image_list); image_list = NULL; } } return (image_list); } void NBImageList_print(NBImageListRef image_list) { int count; int i; printf("%-12s %-35s %-35s %-10s %-9s %-12s Image(s)\n", "Sharepoint", "Dir", "Name", "Identifier", "Type", "BootFile"); count = dynarray_count(&image_list->list); for (i = 0; i < count; i++) { NBImageEntryRef entry; entry = (NBImageEntryRef)dynarray_element(&image_list->list, i); NBImageEntry_print(entry); } return; } #ifdef TEST_NBIMAGES #if 0 #include void my_log(int priority, const char *message, ...) { va_list ap; if (priority == LOG_DEBUG) { if (G_verbose == FALSE) return; priority = LOG_INFO; } va_start(ap, message); vsyslog(priority, message, ap); return; } #endif 0 int main(int argc, char * argv[]) { NBSPListRef sharepoints; sharepoints = NBSPList_init(NETBOOT_SHAREPOINT_LINK, NBSP_READONLY_OK); if (sharepoints != NULL) { NBImageListRef images; images = NBImageList_init(sharepoints, argc == 1); if (images != NULL) { NBImageList_print(images); NBImageList_free(&images); } NBSPList_free(&sharepoints); } exit(0); } #endif TEST_NBIMAGES