1228753Smm/*- 2228753Smm * Copyright (c) 2007 Kai Wang 3228753Smm * Copyright (c) 2007 Tim Kientzle 4228753Smm * All rights reserved. 5228753Smm * 6228753Smm * Redistribution and use in source and binary forms, with or without 7228753Smm * modification, are permitted provided that the following conditions 8228753Smm * are met: 9228753Smm * 1. Redistributions of source code must retain the above copyright 10228753Smm * notice, this list of conditions and the following disclaimer 11228753Smm * in this position and unchanged. 12228753Smm * 2. Redistributions in binary form must reproduce the above copyright 13228753Smm * notice, this list of conditions and the following disclaimer in the 14228753Smm * documentation and/or other materials provided with the distribution. 15228753Smm * 16228753Smm * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR 17228753Smm * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 18228753Smm * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 19228753Smm * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, 20228753Smm * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 21228753Smm * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 22228753Smm * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 23228753Smm * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24228753Smm * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 25228753Smm * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26228753Smm */ 27228753Smm 28228753Smm#include "archive_platform.h" 29228763Smm__FBSDID("$FreeBSD: stable/10/contrib/libarchive/libarchive/archive_write_set_format_ar.c 358090 2020-02-19 01:51:44Z mm $"); 30228753Smm 31228753Smm#ifdef HAVE_ERRNO_H 32228753Smm#include <errno.h> 33228753Smm#endif 34228753Smm#ifdef HAVE_STDLIB_H 35228753Smm#include <stdlib.h> 36228753Smm#endif 37228753Smm#ifdef HAVE_STRING_H 38228753Smm#include <string.h> 39228753Smm#endif 40228753Smm 41228753Smm#include "archive.h" 42228753Smm#include "archive_entry.h" 43228753Smm#include "archive_private.h" 44228753Smm#include "archive_write_private.h" 45358090Smm#include "archive_write_set_format_private.h" 46228753Smm 47228753Smmstruct ar_w { 48228753Smm uint64_t entry_bytes_remaining; 49228753Smm uint64_t entry_padding; 50228753Smm int is_strtab; 51228753Smm int has_strtab; 52232153Smm char wrote_global_header; 53228753Smm char *strtab; 54228753Smm}; 55228753Smm 56228753Smm/* 57228753Smm * Define structure of the "ar" header. 58228753Smm */ 59228753Smm#define AR_name_offset 0 60228753Smm#define AR_name_size 16 61228753Smm#define AR_date_offset 16 62228753Smm#define AR_date_size 12 63228753Smm#define AR_uid_offset 28 64228753Smm#define AR_uid_size 6 65228753Smm#define AR_gid_offset 34 66228753Smm#define AR_gid_size 6 67228753Smm#define AR_mode_offset 40 68228753Smm#define AR_mode_size 8 69228753Smm#define AR_size_offset 48 70228753Smm#define AR_size_size 10 71228753Smm#define AR_fmag_offset 58 72228753Smm#define AR_fmag_size 2 73228753Smm 74228753Smmstatic int archive_write_set_format_ar(struct archive_write *); 75228753Smmstatic int archive_write_ar_header(struct archive_write *, 76228753Smm struct archive_entry *); 77228753Smmstatic ssize_t archive_write_ar_data(struct archive_write *, 78228753Smm const void *buff, size_t s); 79232153Smmstatic int archive_write_ar_free(struct archive_write *); 80232153Smmstatic int archive_write_ar_close(struct archive_write *); 81228753Smmstatic int archive_write_ar_finish_entry(struct archive_write *); 82228753Smmstatic const char *ar_basename(const char *path); 83228753Smmstatic int format_octal(int64_t v, char *p, int s); 84228753Smmstatic int format_decimal(int64_t v, char *p, int s); 85228753Smm 86228753Smmint 87228753Smmarchive_write_set_format_ar_bsd(struct archive *_a) 88228753Smm{ 89228753Smm struct archive_write *a = (struct archive_write *)_a; 90232153Smm int r; 91232153Smm 92232153Smm archive_check_magic(_a, ARCHIVE_WRITE_MAGIC, 93232153Smm ARCHIVE_STATE_NEW, "archive_write_set_format_ar_bsd"); 94232153Smm r = archive_write_set_format_ar(a); 95228753Smm if (r == ARCHIVE_OK) { 96228753Smm a->archive.archive_format = ARCHIVE_FORMAT_AR_BSD; 97228753Smm a->archive.archive_format_name = "ar (BSD)"; 98228753Smm } 99228753Smm return (r); 100228753Smm} 101228753Smm 102228753Smmint 103228753Smmarchive_write_set_format_ar_svr4(struct archive *_a) 104228753Smm{ 105228753Smm struct archive_write *a = (struct archive_write *)_a; 106232153Smm int r; 107232153Smm 108232153Smm archive_check_magic(_a, ARCHIVE_WRITE_MAGIC, 109232153Smm ARCHIVE_STATE_NEW, "archive_write_set_format_ar_svr4"); 110232153Smm r = archive_write_set_format_ar(a); 111228753Smm if (r == ARCHIVE_OK) { 112228753Smm a->archive.archive_format = ARCHIVE_FORMAT_AR_GNU; 113228753Smm a->archive.archive_format_name = "ar (GNU/SVR4)"; 114228753Smm } 115228753Smm return (r); 116228753Smm} 117228753Smm 118228753Smm/* 119228753Smm * Generic initialization. 120228753Smm */ 121228753Smmstatic int 122228753Smmarchive_write_set_format_ar(struct archive_write *a) 123228753Smm{ 124228753Smm struct ar_w *ar; 125228753Smm 126228753Smm /* If someone else was already registered, unregister them. */ 127232153Smm if (a->format_free != NULL) 128232153Smm (a->format_free)(a); 129228753Smm 130311042Smm ar = (struct ar_w *)calloc(1, sizeof(*ar)); 131228753Smm if (ar == NULL) { 132228753Smm archive_set_error(&a->archive, ENOMEM, "Can't allocate ar data"); 133228753Smm return (ARCHIVE_FATAL); 134228753Smm } 135228753Smm a->format_data = ar; 136228753Smm 137228753Smm a->format_name = "ar"; 138228753Smm a->format_write_header = archive_write_ar_header; 139228753Smm a->format_write_data = archive_write_ar_data; 140232153Smm a->format_close = archive_write_ar_close; 141232153Smm a->format_free = archive_write_ar_free; 142228753Smm a->format_finish_entry = archive_write_ar_finish_entry; 143228753Smm return (ARCHIVE_OK); 144228753Smm} 145228753Smm 146228753Smmstatic int 147228753Smmarchive_write_ar_header(struct archive_write *a, struct archive_entry *entry) 148228753Smm{ 149228753Smm int ret, append_fn; 150228753Smm char buff[60]; 151228753Smm char *ss, *se; 152228753Smm struct ar_w *ar; 153228753Smm const char *pathname; 154228753Smm const char *filename; 155228753Smm int64_t size; 156228753Smm 157228753Smm append_fn = 0; 158228753Smm ar = (struct ar_w *)a->format_data; 159228753Smm ar->is_strtab = 0; 160228753Smm filename = NULL; 161228753Smm size = archive_entry_size(entry); 162228753Smm 163228753Smm 164228753Smm /* 165228753Smm * Reject files with empty name. 166228753Smm */ 167228753Smm pathname = archive_entry_pathname(entry); 168248616Smm if (pathname == NULL || *pathname == '\0') { 169228753Smm archive_set_error(&a->archive, EINVAL, 170228753Smm "Invalid filename"); 171228753Smm return (ARCHIVE_WARN); 172228753Smm } 173228753Smm 174228753Smm /* 175228753Smm * If we are now at the beginning of the archive, 176228753Smm * we need first write the ar global header. 177228753Smm */ 178232153Smm if (!ar->wrote_global_header) { 179232153Smm __archive_write_output(a, "!<arch>\n", 8); 180232153Smm ar->wrote_global_header = 1; 181232153Smm } 182228753Smm 183228753Smm memset(buff, ' ', 60); 184337352Smm memcpy(&buff[AR_fmag_offset], "`\n", 2); 185228753Smm 186228753Smm if (strcmp(pathname, "/") == 0 ) { 187228753Smm /* Entry is archive symbol table in GNU format */ 188228753Smm buff[AR_name_offset] = '/'; 189228753Smm goto stat; 190228753Smm } 191344674Smm if (strcmp(pathname, "/SYM64/") == 0) { 192344674Smm /* Entry is archive symbol table in GNU 64-bit format */ 193344674Smm memcpy(buff + AR_name_offset, "/SYM64/", 7); 194344674Smm goto stat; 195344674Smm } 196228753Smm if (strcmp(pathname, "__.SYMDEF") == 0) { 197228753Smm /* Entry is archive symbol table in BSD format */ 198337352Smm memcpy(buff + AR_name_offset, "__.SYMDEF", 9); 199228753Smm goto stat; 200228753Smm } 201228753Smm if (strcmp(pathname, "//") == 0) { 202228753Smm /* 203228753Smm * Entry is archive filename table, inform that we should 204228753Smm * collect strtab in next _data call. 205228753Smm */ 206228753Smm ar->is_strtab = 1; 207228753Smm buff[AR_name_offset] = buff[AR_name_offset + 1] = '/'; 208228753Smm /* 209232153Smm * For archive string table, only ar_size field should 210228753Smm * be set. 211228753Smm */ 212228753Smm goto size; 213228753Smm } 214228753Smm 215228753Smm /* 216228753Smm * Otherwise, entry is a normal archive member. 217228753Smm * Strip leading paths from filenames, if any. 218228753Smm */ 219228753Smm if ((filename = ar_basename(pathname)) == NULL) { 220228753Smm /* Reject filenames with trailing "/" */ 221228753Smm archive_set_error(&a->archive, EINVAL, 222228753Smm "Invalid filename"); 223228753Smm return (ARCHIVE_WARN); 224228753Smm } 225228753Smm 226228753Smm if (a->archive.archive_format == ARCHIVE_FORMAT_AR_GNU) { 227228753Smm /* 228228753Smm * SVR4/GNU variant use a "/" to mark then end of the filename, 229228753Smm * make it possible to have embedded spaces in the filename. 230228753Smm * So, the longest filename here (without extension) is 231228753Smm * actually 15 bytes. 232228753Smm */ 233228753Smm if (strlen(filename) <= 15) { 234337352Smm memcpy(&buff[AR_name_offset], 235228753Smm filename, strlen(filename)); 236228753Smm buff[AR_name_offset + strlen(filename)] = '/'; 237228753Smm } else { 238228753Smm /* 239228753Smm * For filename longer than 15 bytes, GNU variant 240228753Smm * makes use of a string table and instead stores the 241228753Smm * offset of the real filename to in the ar_name field. 242228753Smm * The string table should have been written before. 243228753Smm */ 244228753Smm if (ar->has_strtab <= 0) { 245228753Smm archive_set_error(&a->archive, EINVAL, 246228753Smm "Can't find string table"); 247228753Smm return (ARCHIVE_WARN); 248228753Smm } 249228753Smm 250228753Smm se = (char *)malloc(strlen(filename) + 3); 251228753Smm if (se == NULL) { 252228753Smm archive_set_error(&a->archive, ENOMEM, 253228753Smm "Can't allocate filename buffer"); 254228753Smm return (ARCHIVE_FATAL); 255228753Smm } 256228753Smm 257337352Smm memcpy(se, filename, strlen(filename)); 258228753Smm strcpy(se + strlen(filename), "/\n"); 259228753Smm 260228753Smm ss = strstr(ar->strtab, se); 261228753Smm free(se); 262228753Smm 263228753Smm if (ss == NULL) { 264228753Smm archive_set_error(&a->archive, EINVAL, 265228753Smm "Invalid string table"); 266228753Smm return (ARCHIVE_WARN); 267228753Smm } 268228753Smm 269228753Smm /* 270228753Smm * GNU variant puts "/" followed by digits into 271228753Smm * ar_name field. These digits indicates the real 272228753Smm * filename string's offset to the string table. 273228753Smm */ 274228753Smm buff[AR_name_offset] = '/'; 275228753Smm if (format_decimal(ss - ar->strtab, 276228753Smm buff + AR_name_offset + 1, 277228753Smm AR_name_size - 1)) { 278228753Smm archive_set_error(&a->archive, ERANGE, 279228753Smm "string table offset too large"); 280228753Smm return (ARCHIVE_WARN); 281228753Smm } 282228753Smm } 283228753Smm } else if (a->archive.archive_format == ARCHIVE_FORMAT_AR_BSD) { 284228753Smm /* 285228753Smm * BSD variant: for any file name which is more than 286228753Smm * 16 chars or contains one or more embedded space(s), the 287228753Smm * string "#1/" followed by the ASCII length of the name is 288228753Smm * put into the ar_name field. The file size (stored in the 289228753Smm * ar_size field) is incremented by the length of the name. 290228753Smm * The name is then written immediately following the 291228753Smm * archive header. 292228753Smm */ 293228753Smm if (strlen(filename) <= 16 && strchr(filename, ' ') == NULL) { 294337352Smm memcpy(&buff[AR_name_offset], filename, strlen(filename)); 295228753Smm buff[AR_name_offset + strlen(filename)] = ' '; 296228753Smm } 297228753Smm else { 298337352Smm memcpy(buff + AR_name_offset, "#1/", 3); 299228753Smm if (format_decimal(strlen(filename), 300228753Smm buff + AR_name_offset + 3, 301228753Smm AR_name_size - 3)) { 302228753Smm archive_set_error(&a->archive, ERANGE, 303228753Smm "File name too long"); 304228753Smm return (ARCHIVE_WARN); 305228753Smm } 306228753Smm append_fn = 1; 307228753Smm size += strlen(filename); 308228753Smm } 309228753Smm } 310228753Smm 311228753Smmstat: 312228753Smm if (format_decimal(archive_entry_mtime(entry), buff + AR_date_offset, AR_date_size)) { 313228753Smm archive_set_error(&a->archive, ERANGE, 314228753Smm "File modification time too large"); 315228753Smm return (ARCHIVE_WARN); 316228753Smm } 317228753Smm if (format_decimal(archive_entry_uid(entry), buff + AR_uid_offset, AR_uid_size)) { 318228753Smm archive_set_error(&a->archive, ERANGE, 319228753Smm "Numeric user ID too large"); 320228753Smm return (ARCHIVE_WARN); 321228753Smm } 322228753Smm if (format_decimal(archive_entry_gid(entry), buff + AR_gid_offset, AR_gid_size)) { 323228753Smm archive_set_error(&a->archive, ERANGE, 324228753Smm "Numeric group ID too large"); 325228753Smm return (ARCHIVE_WARN); 326228753Smm } 327228753Smm if (format_octal(archive_entry_mode(entry), buff + AR_mode_offset, AR_mode_size)) { 328228753Smm archive_set_error(&a->archive, ERANGE, 329228753Smm "Numeric mode too large"); 330228753Smm return (ARCHIVE_WARN); 331228753Smm } 332228753Smm /* 333228753Smm * Sanity Check: A non-pseudo archive member should always be 334228753Smm * a regular file. 335228753Smm */ 336228753Smm if (filename != NULL && archive_entry_filetype(entry) != AE_IFREG) { 337228753Smm archive_set_error(&a->archive, EINVAL, 338228753Smm "Regular file required for non-pseudo member"); 339228753Smm return (ARCHIVE_WARN); 340228753Smm } 341228753Smm 342228753Smmsize: 343228753Smm if (format_decimal(size, buff + AR_size_offset, AR_size_size)) { 344228753Smm archive_set_error(&a->archive, ERANGE, 345228753Smm "File size out of range"); 346228753Smm return (ARCHIVE_WARN); 347228753Smm } 348228753Smm 349232153Smm ret = __archive_write_output(a, buff, 60); 350228753Smm if (ret != ARCHIVE_OK) 351228753Smm return (ret); 352228753Smm 353228753Smm ar->entry_bytes_remaining = size; 354228753Smm ar->entry_padding = ar->entry_bytes_remaining % 2; 355228753Smm 356228753Smm if (append_fn > 0) { 357232153Smm ret = __archive_write_output(a, filename, strlen(filename)); 358228753Smm if (ret != ARCHIVE_OK) 359228753Smm return (ret); 360228753Smm ar->entry_bytes_remaining -= strlen(filename); 361228753Smm } 362228753Smm 363228753Smm return (ARCHIVE_OK); 364228753Smm} 365228753Smm 366228753Smmstatic ssize_t 367228753Smmarchive_write_ar_data(struct archive_write *a, const void *buff, size_t s) 368228753Smm{ 369228753Smm struct ar_w *ar; 370228753Smm int ret; 371228753Smm 372228753Smm ar = (struct ar_w *)a->format_data; 373228753Smm if (s > ar->entry_bytes_remaining) 374238856Smm s = (size_t)ar->entry_bytes_remaining; 375228753Smm 376228753Smm if (ar->is_strtab > 0) { 377228753Smm if (ar->has_strtab > 0) { 378228753Smm archive_set_error(&a->archive, EINVAL, 379228753Smm "More than one string tables exist"); 380228753Smm return (ARCHIVE_WARN); 381228753Smm } 382228753Smm 383322072Smm ar->strtab = (char *)malloc(s + 1); 384228753Smm if (ar->strtab == NULL) { 385228753Smm archive_set_error(&a->archive, ENOMEM, 386228753Smm "Can't allocate strtab buffer"); 387228753Smm return (ARCHIVE_FATAL); 388228753Smm } 389322072Smm memcpy(ar->strtab, buff, s); 390322072Smm ar->strtab[s] = '\0'; 391228753Smm ar->has_strtab = 1; 392228753Smm } 393228753Smm 394232153Smm ret = __archive_write_output(a, buff, s); 395228753Smm if (ret != ARCHIVE_OK) 396228753Smm return (ret); 397228753Smm 398228753Smm ar->entry_bytes_remaining -= s; 399228753Smm return (s); 400228753Smm} 401228753Smm 402228753Smmstatic int 403232153Smmarchive_write_ar_free(struct archive_write *a) 404228753Smm{ 405228753Smm struct ar_w *ar; 406228753Smm 407228753Smm ar = (struct ar_w *)a->format_data; 408228753Smm 409228753Smm if (ar == NULL) 410228753Smm return (ARCHIVE_OK); 411228753Smm 412228753Smm if (ar->has_strtab > 0) { 413228753Smm free(ar->strtab); 414228753Smm ar->strtab = NULL; 415228753Smm } 416228753Smm 417228753Smm free(ar); 418228753Smm a->format_data = NULL; 419228753Smm return (ARCHIVE_OK); 420228753Smm} 421228753Smm 422228753Smmstatic int 423232153Smmarchive_write_ar_close(struct archive_write *a) 424228753Smm{ 425232153Smm struct ar_w *ar; 426228753Smm int ret; 427228753Smm 428228753Smm /* 429228753Smm * If we haven't written anything yet, we need to write 430228753Smm * the ar global header now to make it a valid ar archive. 431228753Smm */ 432232153Smm ar = (struct ar_w *)a->format_data; 433232153Smm if (!ar->wrote_global_header) { 434232153Smm ar->wrote_global_header = 1; 435232153Smm ret = __archive_write_output(a, "!<arch>\n", 8); 436228753Smm return (ret); 437228753Smm } 438228753Smm 439228753Smm return (ARCHIVE_OK); 440228753Smm} 441228753Smm 442228753Smmstatic int 443228753Smmarchive_write_ar_finish_entry(struct archive_write *a) 444228753Smm{ 445228753Smm struct ar_w *ar; 446228753Smm int ret; 447228753Smm 448228753Smm ar = (struct ar_w *)a->format_data; 449228753Smm 450228753Smm if (ar->entry_bytes_remaining != 0) { 451228753Smm archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, 452228753Smm "Entry remaining bytes larger than 0"); 453228753Smm return (ARCHIVE_WARN); 454228753Smm } 455228753Smm 456228753Smm if (ar->entry_padding == 0) { 457228753Smm return (ARCHIVE_OK); 458228753Smm } 459228753Smm 460228753Smm if (ar->entry_padding != 1) { 461228753Smm archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, 462232153Smm "Padding wrong size: %ju should be 1 or 0", 463232153Smm (uintmax_t)ar->entry_padding); 464228753Smm return (ARCHIVE_WARN); 465228753Smm } 466228753Smm 467232153Smm ret = __archive_write_output(a, "\n", 1); 468228753Smm return (ret); 469228753Smm} 470228753Smm 471228753Smm/* 472228753Smm * Format a number into the specified field using base-8. 473228753Smm * NB: This version is slightly different from the one in 474228753Smm * _ustar.c 475228753Smm */ 476228753Smmstatic int 477228753Smmformat_octal(int64_t v, char *p, int s) 478228753Smm{ 479228753Smm int len; 480228753Smm char *h; 481228753Smm 482228753Smm len = s; 483228753Smm h = p; 484228753Smm 485228753Smm /* Octal values can't be negative, so use 0. */ 486228753Smm if (v < 0) { 487228753Smm while (len-- > 0) 488228753Smm *p++ = '0'; 489228753Smm return (-1); 490228753Smm } 491228753Smm 492228753Smm p += s; /* Start at the end and work backwards. */ 493228753Smm do { 494228753Smm *--p = (char)('0' + (v & 7)); 495228753Smm v >>= 3; 496228753Smm } while (--s > 0 && v > 0); 497228753Smm 498228753Smm if (v == 0) { 499228753Smm memmove(h, p, len - s); 500228753Smm p = h + len - s; 501228753Smm while (s-- > 0) 502228753Smm *p++ = ' '; 503228753Smm return (0); 504228753Smm } 505228753Smm /* If it overflowed, fill field with max value. */ 506228753Smm while (len-- > 0) 507228753Smm *p++ = '7'; 508228753Smm 509228753Smm return (-1); 510228753Smm} 511228753Smm 512228753Smm/* 513228753Smm * Format a number into the specified field using base-10. 514228753Smm */ 515228753Smmstatic int 516228753Smmformat_decimal(int64_t v, char *p, int s) 517228753Smm{ 518228753Smm int len; 519228753Smm char *h; 520228753Smm 521228753Smm len = s; 522228753Smm h = p; 523228753Smm 524232153Smm /* Negative values in ar header are meaningless, so use 0. */ 525228753Smm if (v < 0) { 526228753Smm while (len-- > 0) 527228753Smm *p++ = '0'; 528228753Smm return (-1); 529228753Smm } 530228753Smm 531228753Smm p += s; 532228753Smm do { 533228753Smm *--p = (char)('0' + (v % 10)); 534228753Smm v /= 10; 535228753Smm } while (--s > 0 && v > 0); 536228753Smm 537228753Smm if (v == 0) { 538228753Smm memmove(h, p, len - s); 539228753Smm p = h + len - s; 540228753Smm while (s-- > 0) 541228753Smm *p++ = ' '; 542228753Smm return (0); 543228753Smm } 544228753Smm /* If it overflowed, fill field with max value. */ 545228753Smm while (len-- > 0) 546228753Smm *p++ = '9'; 547228753Smm 548228753Smm return (-1); 549228753Smm} 550228753Smm 551228753Smmstatic const char * 552228753Smmar_basename(const char *path) 553228753Smm{ 554228753Smm const char *endp, *startp; 555228753Smm 556228753Smm endp = path + strlen(path) - 1; 557228753Smm /* 558228753Smm * For filename with trailing slash(es), we return 559228753Smm * NULL indicating an error. 560228753Smm */ 561228753Smm if (*endp == '/') 562228753Smm return (NULL); 563228753Smm 564228753Smm /* Find the start of the base */ 565228753Smm startp = endp; 566228753Smm while (startp > path && *(startp - 1) != '/') 567228753Smm startp--; 568228753Smm 569228753Smm return (startp); 570228753Smm} 571