static int ismt_access(struct i2c_adapter *adap, u16 addr,
		       unsigned short flags, char read_write, u8 command,
		       int size, union i2c_smbus_data *data)
{
	int ret;
	unsigned long time_left;
	dma_addr_t dma_addr = 0; /* address of the data buffer */
	u8 dma_size = 0;
	enum dma_data_direction dma_direction = 0;
	struct ismt_desc *desc;
	struct ismt_priv *priv = i2c_get_adapdata(adap);
	struct device *dev = &priv->pci_dev->dev;
	u8 *dma_buffer = PTR_ALIGN(&priv->buffer[0], 16);

	desc = &priv->hw[priv->head];

	/* Initialize the DMA buffer */
	memset(priv->buffer, 0, sizeof(priv->buffer));

	/* Initialize the descriptor */
	memset(desc, 0, sizeof(struct ismt_desc));
	desc->tgtaddr_rw = ISMT_DESC_ADDR_RW(addr, read_write);

	/* Always clear the log entries */
	memset(priv->log, 0, ISMT_LOG_ENTRIES * sizeof(u32));

	/* Initialize common control bits */
	if (likely(pci_dev_msi_enabled(priv->pci_dev)))
		desc->control = ISMT_DESC_INT | ISMT_DESC_FAIR;
	else
		desc->control = ISMT_DESC_FAIR;

	if ((flags & I2C_CLIENT_PEC) && (size != I2C_SMBUS_QUICK)
	    && (size != I2C_SMBUS_I2C_BLOCK_DATA))
		desc->control |= ISMT_DESC_PEC;

	switch (size) {
	case I2C_SMBUS_QUICK:
		dev_dbg(dev, "I2C_SMBUS_QUICK\n");
		break;

	case I2C_SMBUS_BYTE:
		if (read_write == I2C_SMBUS_WRITE) {
			/*
			 * Send Byte
			 * The command field contains the write data
			 */
			dev_dbg(dev, "I2C_SMBUS_BYTE:  WRITE\n");
			desc->control |= ISMT_DESC_CWRL;
			desc->wr_len_cmd = command;
		} else {
			/* Receive Byte */
			dev_dbg(dev, "I2C_SMBUS_BYTE:  READ\n");
			dma_size = 1;
			dma_direction = DMA_FROM_DEVICE;
			desc->rd_len = 1;
		}
		break;

	case I2C_SMBUS_BYTE_DATA:
		if (read_write == I2C_SMBUS_WRITE) {
			/*
			 * Write Byte
			 * Command plus 1 data byte
			 */
			dev_dbg(dev, "I2C_SMBUS_BYTE_DATA:  WRITE\n");
			desc->wr_len_cmd = 2;
			dma_size = 2;
			dma_direction = DMA_TO_DEVICE;
			dma_buffer[0] = command;
			dma_buffer[1] = data->byte;
		} else {
			/* Read Byte */
			dev_dbg(dev, "I2C_SMBUS_BYTE_DATA:  READ\n");
			desc->control |= ISMT_DESC_CWRL;
			desc->wr_len_cmd = command;
			desc->rd_len = 1;
			dma_size = 1;
			dma_direction = DMA_FROM_DEVICE;
		}
		break;

	case I2C_SMBUS_WORD_DATA:
		if (read_write == I2C_SMBUS_WRITE) {
			/* Write Word */
			dev_dbg(dev, "I2C_SMBUS_WORD_DATA:  WRITE\n");
			desc->wr_len_cmd = 3;
			dma_size = 3;
			dma_direction = DMA_TO_DEVICE;
			dma_buffer[0] = command;
			dma_buffer[1] = data->word & 0xff;
			dma_buffer[2] = data->word >> 8;
		} else {
			/* Read Word */
			dev_dbg(dev, "I2C_SMBUS_WORD_DATA:  READ\n");
			desc->wr_len_cmd = command;
			desc->control |= ISMT_DESC_CWRL;
			desc->rd_len = 2;
			dma_size = 2;
			dma_direction = DMA_FROM_DEVICE;
		}
		break;

	case I2C_SMBUS_PROC_CALL:
		dev_dbg(dev, "I2C_SMBUS_PROC_CALL\n");
		desc->wr_len_cmd = 3;
		desc->rd_len = 2;
		dma_size = 3;
		dma_direction = DMA_BIDIRECTIONAL;
		dma_buffer[0] = command;
		dma_buffer[1] = data->word & 0xff;
		dma_buffer[2] = data->word >> 8;
		break;

	case I2C_SMBUS_BLOCK_DATA:
		if (read_write == I2C_SMBUS_WRITE) {
			/* Block Write */
			dev_dbg(dev, "I2C_SMBUS_BLOCK_DATA:  WRITE\n");
			dma_size = data->block[0] + 1;
			dma_direction = DMA_TO_DEVICE;
			desc->wr_len_cmd = dma_size;
			desc->control |= ISMT_DESC_BLK;
			dma_buffer[0] = command;
			memcpy(&dma_buffer[1], &data->block[1], dma_size - 1);
		} else {
			/* Block Read */
			dev_dbg(dev, "I2C_SMBUS_BLOCK_DATA:  READ\n");
			dma_size = I2C_SMBUS_BLOCK_MAX;
			dma_direction = DMA_FROM_DEVICE;
			desc->rd_len = dma_size;
			desc->wr_len_cmd = command;
			desc->control |= (ISMT_DESC_BLK | ISMT_DESC_CWRL);
		}
		break;

	case I2C_SMBUS_BLOCK_PROC_CALL:
		dev_dbg(dev, "I2C_SMBUS_BLOCK_PROC_CALL\n");
		if (data->block[0] > I2C_SMBUS_BLOCK_MAX)
			return -EINVAL;

		dma_size = I2C_SMBUS_BLOCK_MAX;
		desc->tgtaddr_rw = ISMT_DESC_ADDR_RW(addr, 1);
		desc->wr_len_cmd = data->block[0] + 1;
		desc->rd_len = dma_size;
		desc->control |= ISMT_DESC_BLK;
		dma_direction = DMA_BIDIRECTIONAL;
		dma_buffer[0] = command;
		memcpy(&dma_buffer[1], &data->block[1], data->block[0]);
		break;

	case I2C_SMBUS_I2C_BLOCK_DATA:
		/* Make sure the length is valid */
		if (data->block[0] < 1)
			data->block[0] = 1;

		if (data->block[0] > I2C_SMBUS_BLOCK_MAX)
			data->block[0] = I2C_SMBUS_BLOCK_MAX;

		if (read_write == I2C_SMBUS_WRITE) {
			/* i2c Block Write */
			dev_dbg(dev, "I2C_SMBUS_I2C_BLOCK_DATA:  WRITE\n");
			dma_size = data->block[0] + 1;
			dma_direction = DMA_TO_DEVICE;
			desc->wr_len_cmd = dma_size;
			desc->control |= ISMT_DESC_I2C;
			dma_buffer[0] = command;
			memcpy(&dma_buffer[1], &data->block[1], dma_size - 1);
		} else {
			/* i2c Block Read */
			dev_dbg(dev, "I2C_SMBUS_I2C_BLOCK_DATA:  READ\n");
			dma_size = data->block[0];
			dma_direction = DMA_FROM_DEVICE;
			desc->rd_len = dma_size;
			desc->wr_len_cmd = command;
			desc->control |= (ISMT_DESC_I2C | ISMT_DESC_CWRL);
			/*
			 * Per the "Table 15-15. I2C Commands",
			 * in the External Design Specification (EDS),
			 * (Document Number: 508084, Revision: 2.0),
			 * the _rw bit must be 0
			 */
			desc->tgtaddr_rw = ISMT_DESC_ADDR_RW(addr, 0);
		}
		break;

	default:
		dev_err(dev, "Unsupported transaction %d\n",
			size);
		return -EOPNOTSUPP;
	}

	/* map the data buffer */
	if (dma_size != 0) {
		dev_dbg(dev, " dev=%p\n", dev);
		dev_dbg(dev, " data=%p\n", data);
		dev_dbg(dev, " dma_buffer=%p\n", dma_buffer);
		dev_dbg(dev, " dma_size=%d\n", dma_size);
		dev_dbg(dev, " dma_direction=%d\n", dma_direction);

		dma_addr = dma_map_single(dev,
				      dma_buffer,
				      dma_size,
				      dma_direction);

		if (dma_mapping_error(dev, dma_addr)) {
			dev_err(dev, "Error in mapping dma buffer %p\n",
				dma_buffer);
			return -EIO;
		}

		dev_dbg(dev, " dma_addr = %pad\n", &dma_addr);

		desc->dptr_low = lower_32_bits(dma_addr);
		desc->dptr_high = upper_32_bits(dma_addr);
	}

	reinit_completion(&priv->cmp);

	/* Add the descriptor */
	ismt_submit_desc(priv);

	/* Now we wait for interrupt completion, 1s */
	time_left = wait_for_completion_timeout(&priv->cmp, HZ*1);

	/* unmap the data buffer */
	if (dma_size != 0)
		dma_unmap_single(dev, dma_addr, dma_size, dma_direction);

	if (unlikely(!time_left)) {
		dev_err(dev, "completion wait timed out\n");
		ret = -ETIMEDOUT;
		goto out;
	}

	/* do any post processing of the descriptor here */
	ret = ismt_process_desc(desc, data, priv, size, read_write);

out:
	/* Update the ring pointer */
	priv->head++;
	priv->head %= ISMT_DESC_ENTRIES;

	return ret;
}


i2c: ismt: prevent memory corruption in ismt_access()

The "data->block[0]" variable comes from the user and is a number
between 0-255.  It needs to be capped to prevent writing beyond the end
of dma_buffer[].

Fixes: 5e9a97b1f449 ("i2c: ismt: Adding support for I2C_SMBUS_BLOCK_PROC_CALL")
Reported-and-tested-by: Zheyu Ma <zheyuma97@gmail.com>
Signed-off-by: Dan Carpenter <dan.carpenter@oracle.com>
Reviewed-by: Mika Westerberg <mika.westerberg@linux.intel.com>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>

static void ismt_gen_reg_dump(struct ismt_priv *priv)
{
	struct device *dev = &priv->pci_dev->dev;

	dev_dbg(dev, "Dump of the iSMT General Registers\n");
	dev_dbg(dev, "  GCTRL.... : (0x%p)=0x%X\n",
		priv->smba + ISMT_GR_GCTRL,
		readl(priv->smba + ISMT_GR_GCTRL));
	dev_dbg(dev, "  SMTICL... : (0x%p)=0x%016llX\n",
		priv->smba + ISMT_GR_SMTICL,
		(long long unsigned int)readq(priv->smba + ISMT_GR_SMTICL));
	dev_dbg(dev, "  ERRINTMSK : (0x%p)=0x%X\n",
		priv->smba + ISMT_GR_ERRINTMSK,
		readl(priv->smba + ISMT_GR_ERRINTMSK));
	dev_dbg(dev, "  ERRAERMSK : (0x%p)=0x%X\n",
		priv->smba + ISMT_GR_ERRAERMSK,
		readl(priv->smba + ISMT_GR_ERRAERMSK));
	dev_dbg(dev, "  ERRSTS... : (0x%p)=0x%X\n",
		priv->smba + ISMT_GR_ERRSTS,
		readl(priv->smba + ISMT_GR_ERRSTS));
	dev_dbg(dev, "  ERRINFO.. : (0x%p)=0x%X\n",
		priv->smba + ISMT_GR_ERRINFO,
		readl(priv->smba + ISMT_GR_ERRINFO));
}

A buffer overflow vulnerability was found in the Linux kernel Intel’s iSMT SMBus host controller driver in the way it handled the I2C_SMBUS_BLOCK_PROC_CALL case (via the ioctl I2C_SMBUS) with malicious input data. This flaw could allow a local user to crash the system.


Fix potential out-of-buffer read while parsing corrupt file, closes #38

size_t mobi_get_record_extrasize(const MOBIPdbRecord *record, const uint16_t flags) {
    size_t extra_size = 0;
    MOBIBuffer *buf = mobi_buffer_init_null(record->data, record->size);
    if (buf == NULL) {
        debug_print("%s", "Buffer init in extrasize failed\n");
        return MOBI_NOTSET;
    }
    /* set pointer at the end of the record data */
    mobi_buffer_setpos(buf, buf->maxlen - 1);
    for (int bit = 15; bit > 0; bit--) {
        if (flags & (1 << bit)) {
            /* bit is set */
            size_t len = 0;
            /* size contains varlen itself and optional data */
            const uint32_t size = mobi_buffer_get_varlen_dec(buf, &len);
            /* skip data */
            /* TODO: read and store in record struct */
            mobi_buffer_seek(buf, - (int)(size - len));
            extra_size += size;
        }
    };
    /* check bit 0 */
    if (flags & 1) {
            const uint8_t b = mobi_buffer_get8(buf);
            /* two first bits hold size */
            extra_size += (b & 0x3) + 1;
    }
    mobi_buffer_free_null(buf);
    return extra_size;
}

libmobi is vulnerable to Use of Out-of-range Pointer Offset


MOBI_RET mobi_load_file(MOBIData *m, FILE *file) {
    MOBI_RET ret;
    if (m == NULL) {
        debug_print("%s", "Mobi structure not initialized\n");
        return MOBI_INIT_FAILED;
    }
    ret = mobi_load_pdbheader(m, file);
    if (ret != MOBI_SUCCESS) {
        return ret;
    }
    if (strcmp(m->ph->type, "BOOK") != 0 && strcmp(m->ph->type, "TEXt") != 0) {
        debug_print("Unsupported file type: %s\n", m->ph->type);
        return MOBI_FILE_UNSUPPORTED;
    }
    if (m->ph->rec_count == 0) {
        debug_print("%s", "No records found\n");
        return MOBI_DATA_CORRUPT;
    }
    ret = mobi_load_reclist(m, file);
    if (ret != MOBI_SUCCESS) {
        return ret;
    }
    ret = mobi_load_rec(m, file);
    if (ret != MOBI_SUCCESS) {
        return ret;
    }
    ret = mobi_parse_record0(m, 0);
    if (ret != MOBI_SUCCESS) {
        return ret;
    }
    if (m->rh && m->rh->encryption_type == RECORD0_OLD_ENCRYPTION) {
        /* try to set key for encryption type 1 */
        debug_print("Trying to set key for encryption type 1%s", "\n")
        mobi_drm_setkey(m, NULL);
    }
    /* if EXTH is loaded parse KF8 record0 for hybrid KF7/KF8 file */
    if (m->eh) {
        const size_t boundary_rec_number = mobi_get_kf8boundary_seqnumber(m);
        if (boundary_rec_number != MOBI_NOTSET && boundary_rec_number < UINT32_MAX) {
            /* it is a hybrid KF7/KF8 file */
            m->kf8_boundary_offset = (uint32_t) boundary_rec_number;
            m->next = mobi_init();
            /* link pdb header and records data to KF8data structure */
            m->next->ph = m->ph;
            m->next->rec = m->rec;
            m->next->drm_key = m->drm_key;
            /* close next loop */
            m->next->next = m;
            ret = mobi_parse_record0(m->next, boundary_rec_number + 1);
            if (ret != MOBI_SUCCESS) {
                return ret;
            }
            /* swap to kf8 part if use_kf8 flag is set */
            if (m->use_kf8) {
                mobi_swap_mobidata(m);
            }
        }
    }
    return MOBI_SUCCESS;
}


MOBI_RET mobi_load_reclist(MOBIData *m, FILE *file) {
    if (m == NULL) {
        debug_print("%s", "Mobi structure not initialized\n");
        return MOBI_INIT_FAILED;
    }
    if (!file) {
        debug_print("%s", "File not ready\n");
        return MOBI_FILE_NOT_FOUND;
    }
    m->rec = calloc(1, sizeof(MOBIPdbRecord));
    if (m->rec == NULL) {
        debug_print("%s", "Memory allocation for pdb record failed\n");
        return MOBI_MALLOC_FAILED;
    }
    MOBIPdbRecord *curr = m->rec;
    for (int i = 0; i < m->ph->rec_count; i++) {
        MOBIBuffer *buf = mobi_buffer_init(PALMDB_RECORD_INFO_SIZE);
        if (buf == NULL) {
            debug_print("%s\n", "Memory allocation failed");
            return MOBI_MALLOC_FAILED;
        }
        const size_t len = fread(buf->data, 1, PALMDB_RECORD_INFO_SIZE, file);
        if (len != PALMDB_RECORD_INFO_SIZE) {
            mobi_buffer_free(buf);
            return MOBI_DATA_CORRUPT;
        }
        if (i > 0) {
            curr->next = calloc(1, sizeof(MOBIPdbRecord));
            if (curr->next == NULL) {
                debug_print("%s", "Memory allocation for pdb record failed\n");
                mobi_buffer_free(buf);
                return MOBI_MALLOC_FAILED;
            }
            curr = curr->next;
        }
        curr->offset = mobi_buffer_get32(buf);
        curr->attributes = mobi_buffer_get8(buf);
        const uint8_t h = mobi_buffer_get8(buf);
        const uint16_t l = mobi_buffer_get16(buf);
        curr->uid =  (uint32_t) h << 16 | l;
        curr->next = NULL;
        mobi_buffer_free(buf);
    }
    return MOBI_SUCCESS;
}


MOBI_RET mobi_parse_huff(MOBIHuffCdic *huffcdic, const MOBIPdbRecord *record) {
    MOBIBuffer *buf = mobi_buffer_init_null(record->data, record->size);
    if (buf == NULL) {
        debug_print("%s\n", "Memory allocation failed");
        return MOBI_MALLOC_FAILED;
    }
    char huff_magic[5];
    mobi_buffer_getstring(huff_magic, buf, 4);
    const size_t header_length = mobi_buffer_get32(buf);
    if (strncmp(huff_magic, HUFF_MAGIC, 4) != 0 || header_length < HUFF_HEADER_LEN) {
        debug_print("HUFF wrong magic: %s\n", huff_magic);
        mobi_buffer_free_null(buf);
        return MOBI_DATA_CORRUPT;
    }
    const size_t data1_offset = mobi_buffer_get32(buf);
    const size_t data2_offset = mobi_buffer_get32(buf);
    /* skip little-endian table offsets */
    mobi_buffer_setpos(buf, data1_offset);
    if (buf->offset + (256 * 4) > buf->maxlen) {
        debug_print("%s", "HUFF data1 too short\n");
        mobi_buffer_free_null(buf);
        return MOBI_DATA_CORRUPT;
    }
    /* read 256 indices from data1 big-endian */
    for (int i = 0; i < 256; i++) {
        huffcdic->table1[i] = mobi_buffer_get32(buf);
    }
    mobi_buffer_setpos(buf, data2_offset);
    if (buf->offset + (64 * 4) > buf->maxlen) {
        debug_print("%s", "HUFF data2 too short\n");
        mobi_buffer_free_null(buf);
        return MOBI_DATA_CORRUPT;
    }
    /* read 32 mincode-maxcode pairs from data2 big-endian */
    huffcdic->mincode_table[0] = 0;
    huffcdic->maxcode_table[0] = 0xFFFFFFFF;
    for (int i = 1; i < HUFF_CODETABLE_SIZE; i++) {
        const uint32_t mincode = mobi_buffer_get32(buf);
        const uint32_t maxcode = mobi_buffer_get32(buf);
        huffcdic->mincode_table[i] =  mincode << (32 - i);
        huffcdic->maxcode_table[i] =  ((maxcode + 1) << (32 - i)) - 1;
    }
    mobi_buffer_free_null(buf);
    return MOBI_SUCCESS;
}


MOBI_RET mobi_parse_extheader(MOBIData *m, MOBIBuffer *buf) {
    if (m == NULL) {
        debug_print("%s", "Mobi structure not initialized\n");
        return MOBI_INIT_FAILED;
    }
    char exth_magic[5];
    const size_t header_length = 12;
    mobi_buffer_getstring(exth_magic, buf, 4);
    const size_t exth_length = mobi_buffer_get32(buf) - header_length;
    const size_t rec_count = mobi_buffer_get32(buf);
    if (strncmp(exth_magic, EXTH_MAGIC, 4) != 0 ||
        exth_length + buf->offset > buf->maxlen ||
        rec_count == 0 || rec_count > MOBI_EXTH_MAXCNT) {
        debug_print("%s", "Sanity checks for EXTH header failed\n");
        return MOBI_DATA_CORRUPT;
    }
    const size_t saved_maxlen = buf->maxlen;
    buf->maxlen = exth_length + buf->offset;
    m->eh = calloc(1, sizeof(MOBIExthHeader));
    if (m->eh == NULL) {
        debug_print("%s", "Memory allocation for EXTH header failed\n");
        return MOBI_MALLOC_FAILED;
    }
    MOBIExthHeader *curr = m->eh;
    for (size_t i = 0; i < rec_count; i++) {
        if (curr->data) {
            curr->next = calloc(1, sizeof(MOBIExthHeader));
            if (curr->next == NULL) {
                debug_print("%s", "Memory allocation for EXTH header failed\n");
                mobi_free_eh(m);
                return MOBI_MALLOC_FAILED;
            }
            curr = curr->next;
        }
        curr->tag = mobi_buffer_get32(buf);
        /* data size = record size minus 8 bytes for uid and size */
        curr->size = mobi_buffer_get32(buf) - 8;
        if (curr->size == 0) {
            debug_print("Skip record %i, data too short\n", curr->tag);
            continue;
        }
        if (buf->offset + curr->size > buf->maxlen) {
            debug_print("Record %i too long\n", curr->tag);
            mobi_free_eh(m);
            return MOBI_DATA_CORRUPT;
        }
        curr->data = malloc(curr->size);
        if (curr->data == NULL) {
            debug_print("Memory allocation for EXTH record %i failed\n", curr->tag);
            mobi_free_eh(m);
            return MOBI_MALLOC_FAILED;
        }
        mobi_buffer_getraw(curr->data, buf, curr->size);
        curr->next = NULL;
    }
    buf->maxlen = saved_maxlen;
    return MOBI_SUCCESS;
}


MOBI_RET mobi_load_recdata(MOBIPdbRecord *rec, FILE *file) {
    const int ret = fseek(file, rec->offset, SEEK_SET);
    if (ret != 0) {
        debug_print("Record %i not found\n", rec->uid);
        return MOBI_DATA_CORRUPT;
    }
    rec->data = malloc(rec->size);
    if (rec->data == NULL) {
        debug_print("%s", "Memory allocation for pdb record data failed\n");
        return MOBI_MALLOC_FAILED;
    }
    const size_t len = fread(rec->data, 1, rec->size, file);
    if (len < rec->size) {
        debug_print("Truncated data in record %i\n", rec->uid);
        return MOBI_DATA_CORRUPT;
    }
    return MOBI_SUCCESS;
}


MOBI_RET mobi_parse_fdst(const MOBIData *m, MOBIRawml *rawml) {
    if (m == NULL) {
        debug_print("%s", "Mobi structure not initialized\n");
        return MOBI_INIT_FAILED;
    }
    const size_t fdst_record_number = mobi_get_fdst_record_number(m);
    if (fdst_record_number == MOBI_NOTSET) {
        return MOBI_DATA_CORRUPT;
    }
    const MOBIPdbRecord *fdst_record = mobi_get_record_by_seqnumber(m, fdst_record_number);
    if (fdst_record == NULL) {
        return MOBI_DATA_CORRUPT;
    }
    MOBIBuffer *buf = mobi_buffer_init_null(fdst_record->data, fdst_record->size);
    if (buf == NULL) {
        debug_print("%s\n", "Memory allocation failed");
        return MOBI_MALLOC_FAILED;
    }
    char fdst_magic[5];
    mobi_buffer_getstring(fdst_magic, buf, 4);
    const size_t data_offset = mobi_buffer_get32(buf);
    const size_t section_count = mobi_buffer_get32(buf);
    if (strncmp(fdst_magic, FDST_MAGIC, 4) != 0 ||
        section_count <= 1 ||
        section_count != *m->mh->fdst_section_count ||
        data_offset != 12) {
        debug_print("FDST wrong magic: %s, sections count: %zu or data offset: %zu\n", fdst_magic, section_count, data_offset);
        mobi_buffer_free_null(buf);
        return MOBI_DATA_CORRUPT;
    }
    if ((buf->maxlen - buf->offset) < section_count * 8) {
        debug_print("%s", "Record FDST too short\n");
        mobi_buffer_free_null(buf);
        return MOBI_DATA_CORRUPT;
    }
    rawml->fdst = malloc(sizeof(MOBIFdst));
    if (rawml->fdst == NULL) {
        debug_print("%s\n", "Memory allocation failed");
        mobi_buffer_free_null(buf);
        return MOBI_MALLOC_FAILED;
    }
    rawml->fdst->fdst_section_count = section_count;
    rawml->fdst->fdst_section_starts = malloc(sizeof(*rawml->fdst->fdst_section_starts) * section_count);
    if (rawml->fdst->fdst_section_starts == NULL) {
        debug_print("%s\n", "Memory allocation failed");
        mobi_buffer_free_null(buf);
        free(rawml->fdst);
        rawml->fdst = NULL;
        return MOBI_MALLOC_FAILED;
    }
    rawml->fdst->fdst_section_ends = malloc(sizeof(*rawml->fdst->fdst_section_ends) * section_count);
    if (rawml->fdst->fdst_section_ends == NULL) {
        debug_print("%s\n", "Memory allocation failed");
        mobi_buffer_free_null(buf);
        free(rawml->fdst->fdst_section_starts);
        free(rawml->fdst);
        rawml->fdst = NULL;
        return MOBI_MALLOC_FAILED;
    }
    size_t i = 0;
    while (i < section_count) {
        rawml->fdst->fdst_section_starts[i] = mobi_buffer_get32(buf);
        rawml->fdst->fdst_section_ends[i] = mobi_buffer_get32(buf);
        debug_print("FDST[%zu]:\t%i\t%i\n", i, rawml->fdst->fdst_section_starts[i], rawml->fdst->fdst_section_ends[i]);
        i++;
    }
    mobi_buffer_free_null(buf);
    return MOBI_SUCCESS;
}


MOBI_RET mobi_load_filename(MOBIData *m, const char *path) {
    FILE *file = fopen(path, "rb");
    if (file == NULL) {
        debug_print("%s", "File not found\n");
        return MOBI_FILE_NOT_FOUND;
    }
    const MOBI_RET ret = mobi_load_file(m, file);
    fclose(file);
    return ret;
}


size_t mobi_get_record_mb_extrasize(const MOBIPdbRecord *record, const uint16_t flags) {
    size_t extra_size = 0;
    if (flags & 1) {
        MOBIBuffer *buf = mobi_buffer_init_null(record->data, record->size);
        if (buf == NULL) {
            debug_print("%s", "Buffer init in extrasize failed\n");
            return MOBI_NOTSET;
        }
        /* set pointer at the end of the record data */
        mobi_buffer_setpos(buf, buf->maxlen - 1);
        for (int bit = 15; bit > 0; bit--) {
            if (flags & (1 << bit)) {
                /* bit is set */
                size_t len = 0;
                /* size contains varlen itself and optional data */
                const uint32_t size = mobi_buffer_get_varlen_dec(buf, &len);
                /* skip data */
                /* TODO: read and store in record struct */
                mobi_buffer_seek(buf, - (int)(size - len));
            }
        };
        /* read multibyte section */
        const uint8_t b = mobi_buffer_get8(buf);
        /* two first bits hold size */
        extra_size += (b & 0x3) + 1;
        mobi_buffer_free_null(buf);
    }
    return extra_size;
}


MOBI_RET mobi_parse_huffdic(const MOBIData *m, MOBIHuffCdic *huffcdic) {
    MOBI_RET ret;
    const size_t offset = mobi_get_kf8offset(m);
    if (m->mh == NULL || m->mh->huff_rec_index == NULL || m->mh->huff_rec_count == NULL) {
        debug_print("%s", "HUFF/CDIC records metadata not found in MOBI header\n");
        return MOBI_DATA_CORRUPT;
    }
    const size_t huff_rec_index = *m->mh->huff_rec_index + offset;
    const size_t huff_rec_count = *m->mh->huff_rec_count;
    if (huff_rec_count > HUFF_RECORD_MAXCNT) {
        debug_print("Too many HUFF record (%zu)\n", huff_rec_count);
        return MOBI_DATA_CORRUPT;
    }
    const MOBIPdbRecord *curr = mobi_get_record_by_seqnumber(m, huff_rec_index);
    if (curr == NULL || huff_rec_count < 2) {
        debug_print("%s", "HUFF/CDIC record not found\n");
        return MOBI_DATA_CORRUPT;
    }
    if (curr->size < HUFF_RECORD_MINSIZE) {
        debug_print("HUFF record too short (%zu b)\n", curr->size);
        return MOBI_DATA_CORRUPT;
    }
    ret = mobi_parse_huff(huffcdic, curr);
    if (ret != MOBI_SUCCESS) {
        debug_print("%s", "HUFF parsing failed\n");
        return ret;
    }
    curr = curr->next;
    /* allocate memory for symbols data in each CDIC record */
    huffcdic->symbols = malloc((huff_rec_count - 1) * sizeof(*huffcdic->symbols));
    if (huffcdic->symbols == NULL) {
        debug_print("%s\n", "Memory allocation failed");
        return MOBI_MALLOC_FAILED;
    }
    /* get following CDIC records */
    size_t i = 0;
    while (i < huff_rec_count - 1) {
        if (curr == NULL) {
            debug_print("%s\n", "CDIC record not found");
            return MOBI_DATA_CORRUPT;
        }
        ret = mobi_parse_cdic(huffcdic, curr, i++);
        if (ret != MOBI_SUCCESS) {
            debug_print("%s", "CDIC parsing failed\n");
            return ret;
        }
        curr = curr->next;
    }
    if (huffcdic->index_count != huffcdic->index_read) {
        debug_print("CDIC: wrong read index count: %zu, total: %zu\n", huffcdic->index_read, huffcdic->index_count);
        return MOBI_DATA_CORRUPT;
    }
    return MOBI_SUCCESS;
}


MOBI_RET mobi_load_pdbheader(MOBIData *m, FILE *file) {
    if (m == NULL) {
        debug_print("%s", "Mobi structure not initialized\n");
        return MOBI_INIT_FAILED;
    }
    if (!file) {
        return MOBI_FILE_NOT_FOUND;
    }
    MOBIBuffer *buf = mobi_buffer_init(PALMDB_HEADER_LEN);
    if (buf == NULL) {
        debug_print("%s\n", "Memory allocation failed");
        return MOBI_MALLOC_FAILED;
    }
    const size_t len = fread(buf->data, 1, PALMDB_HEADER_LEN, file);
    if (len != PALMDB_HEADER_LEN) {
        mobi_buffer_free(buf);
        return MOBI_DATA_CORRUPT;
    }
    m->ph = calloc(1, sizeof(MOBIPdbHeader));
    if (m->ph == NULL) {
        debug_print("%s", "Memory allocation for pdb header failed\n");
        mobi_buffer_free(buf);
        return MOBI_MALLOC_FAILED;
    }
    /* parse header */
    mobi_buffer_getstring(m->ph->name, buf, PALMDB_NAME_SIZE_MAX);
    m->ph->attributes = mobi_buffer_get16(buf);
    m->ph->version = mobi_buffer_get16(buf);
    m->ph->ctime = mobi_buffer_get32(buf);
    m->ph->mtime = mobi_buffer_get32(buf);
    m->ph->btime = mobi_buffer_get32(buf);
    m->ph->mod_num = mobi_buffer_get32(buf);
    m->ph->appinfo_offset = mobi_buffer_get32(buf);
    m->ph->sortinfo_offset = mobi_buffer_get32(buf);
    mobi_buffer_getstring(m->ph->type, buf, 4);
    mobi_buffer_getstring(m->ph->creator, buf, 4);
    m->ph->uid = mobi_buffer_get32(buf);
    m->ph->next_rec = mobi_buffer_get32(buf);
    m->ph->rec_count = mobi_buffer_get16(buf);
    mobi_buffer_free(buf);
    return MOBI_SUCCESS;
}


MOBI_RET mobi_load_rec(MOBIData *m, FILE *file) {
    MOBI_RET ret;
    if (m == NULL) {
        debug_print("%s", "Mobi structure not initialized\n");
        return MOBI_INIT_FAILED;
    }
    MOBIPdbRecord *curr = m->rec;
    while (curr != NULL) {
        MOBIPdbRecord *next;
        size_t size;
        if (curr->next != NULL) {
            next = curr->next;
            size = next->offset - curr->offset;
        } else {
            fseek(file, 0, SEEK_END);
            long diff = ftell(file) - curr->offset;
            if (diff <= 0) {
                debug_print("Wrong record size: %li\n", diff);
                return MOBI_DATA_CORRUPT;
            }
            size = (size_t) diff;
            next = NULL;
        }

        curr->size = size;
        ret = mobi_load_recdata(curr, file);
        if (ret  != MOBI_SUCCESS) {
            debug_print("Error loading record uid %i data\n", curr->uid);
            mobi_free_rec(m);
            return ret;
        }
        curr = next;
    }
    return MOBI_SUCCESS;
}


MOBI_RET mobi_parse_cdic(MOBIHuffCdic *huffcdic, const MOBIPdbRecord *record, const size_t num) {
    MOBIBuffer *buf = mobi_buffer_init_null(record->data, record->size);
    if (buf == NULL) {
        debug_print("%s\n", "Memory allocation failed");
        return MOBI_MALLOC_FAILED;
    }
    char cdic_magic[5];
    mobi_buffer_getstring(cdic_magic, buf, 4);
    const size_t header_length = mobi_buffer_get32(buf);
    if (strncmp(cdic_magic, CDIC_MAGIC, 4) != 0 || header_length < CDIC_HEADER_LEN) {
        debug_print("CDIC wrong magic: %s or declared header length: %zu\n", cdic_magic, header_length);
        mobi_buffer_free_null(buf);
        return MOBI_DATA_CORRUPT;
    }
    /* variables in huffcdic initialized to zero with calloc */
    /* save initial count and length */
    size_t index_count = mobi_buffer_get32(buf);
    const size_t code_length = mobi_buffer_get32(buf);
    if (huffcdic->code_length && huffcdic->code_length != code_length) {
        debug_print("CDIC different code length %zu in record %i, previous was %zu\n", huffcdic->code_length, record->uid, code_length);
        mobi_buffer_free_null(buf);
        return MOBI_DATA_CORRUPT;
    }
    if (huffcdic->index_count && huffcdic->index_count != index_count) {
        debug_print("CDIC different index count %zu in record %i, previous was %zu\n", huffcdic->index_count, record->uid, index_count);
        mobi_buffer_free_null(buf);
        return MOBI_DATA_CORRUPT;
    }
    if (code_length == 0 || code_length > HUFF_CODELEN_MAX) {
        debug_print("Code length exceeds sanity checks (%zu)\n", code_length);
        mobi_buffer_free_null(buf);
        return MOBI_DATA_CORRUPT;
    }
    huffcdic->code_length = code_length;
    huffcdic->index_count = index_count;
    if (index_count == 0) {
        debug_print("%s", "CDIC index count is null");
        mobi_buffer_free_null(buf);
        return MOBI_DATA_CORRUPT;
    }
    /* allocate memory for symbol offsets if not already allocated */
    if (num == 0) {
        if (index_count > (1 << HUFF_CODELEN_MAX) * CDIC_RECORD_MAXCNT) {
            debug_print("CDIC index count too large %zu\n", index_count);
            mobi_buffer_free_null(buf);
            return MOBI_DATA_CORRUPT;
        }
        huffcdic->symbol_offsets = malloc(index_count * sizeof(*huffcdic->symbol_offsets));
        if (huffcdic->symbol_offsets == NULL) {
            debug_print("%s", "CDIC cannot allocate memory");
            mobi_buffer_free_null(buf);
            return MOBI_MALLOC_FAILED;
        }
    }
    index_count -= huffcdic->index_read;
    /* limit number of records read to code_length bits */
    if (index_count >> code_length) {
        index_count = (1 << code_length);
    }
    if (buf->offset + (index_count * 2) > buf->maxlen) {
        debug_print("%s", "CDIC indices data too short\n");
        mobi_buffer_free_null(buf);
        return MOBI_DATA_CORRUPT;
    }
    /* read i * 2 byte big-endian indices */
    while (index_count--) {
        const uint16_t offset = mobi_buffer_get16(buf);
        const size_t saved_pos = buf->offset;
        mobi_buffer_setpos(buf, offset + CDIC_HEADER_LEN);
        const size_t len = mobi_buffer_get16(buf) & 0x7fff;
        if (buf->error != MOBI_SUCCESS || buf->offset + len > buf->maxlen) {
            debug_print("%s", "CDIC offset beyond buffer\n");
            mobi_buffer_free_null(buf);
            return MOBI_DATA_CORRUPT;
        }
        mobi_buffer_setpos(buf, saved_pos);
        huffcdic->symbol_offsets[huffcdic->index_read++] = offset;
    }
    if (buf->offset + code_length > buf->maxlen) {
        debug_print("%s", "CDIC dictionary data too short\n");
        mobi_buffer_free_null(buf);
        return MOBI_DATA_CORRUPT;
    }
    /* copy pointer to data */
    huffcdic->symbols[num] = record->data + CDIC_HEADER_LEN;
    /* free buffer */
    mobi_buffer_free_null(buf);
    return MOBI_SUCCESS;
}


MOBI_RET mobi_parse_record0(MOBIData *m, const size_t seqnumber) {
    MOBI_RET ret;
    if (m == NULL) {
        debug_print("%s", "Mobi structure not initialized\n");
        return MOBI_INIT_FAILED;
    }
    const MOBIPdbRecord *record0 = mobi_get_record_by_seqnumber(m, seqnumber);
    if (record0 == NULL) {
        debug_print("%s", "Record 0 not loaded\n");
        return MOBI_DATA_CORRUPT;
    }
    if (record0->size < RECORD0_HEADER_LEN) {
        debug_print("%s", "Record 0 too short\n");
        return MOBI_DATA_CORRUPT;
    }
    MOBIBuffer *buf = mobi_buffer_init_null(record0->data, record0->size);
    if (buf == NULL) {
        debug_print("%s\n", "Memory allocation failed");
        return MOBI_MALLOC_FAILED;
    }
    m->rh = calloc(1, sizeof(MOBIRecord0Header));
    if (m->rh == NULL) {
        debug_print("%s", "Memory allocation for record 0 header failed\n");
        mobi_buffer_free_null(buf);
        return MOBI_MALLOC_FAILED;
    }
    /* parse palmdoc header */
    const uint16_t compression = mobi_buffer_get16(buf);
    mobi_buffer_seek(buf, 2); // unused 2 bytes, zeroes
    if ((compression != RECORD0_NO_COMPRESSION &&
         compression != RECORD0_PALMDOC_COMPRESSION &&
         compression != RECORD0_HUFF_COMPRESSION)) {
        debug_print("Wrong record0 header: %c%c%c%c\n", record0->data[0], record0->data[1], record0->data[2], record0->data[3]);
        mobi_buffer_free_null(buf);
        free(m->rh);
        m->rh = NULL;
        return MOBI_DATA_CORRUPT;
    }
    m->rh->compression_type = compression;
    m->rh->text_length = mobi_buffer_get32(buf);
    m->rh->text_record_count = mobi_buffer_get16(buf);
    m->rh->text_record_size = mobi_buffer_get16(buf);
    m->rh->encryption_type = mobi_buffer_get16(buf);
    m->rh->unknown1 = mobi_buffer_get16(buf);
    if (mobi_is_mobipocket(m)) {
        /* parse mobi header if present  */
        ret = mobi_parse_mobiheader(m, buf);
        if (ret == MOBI_SUCCESS) {
            /* parse exth header if present */
            mobi_parse_extheader(m, buf);
        }
    }
    mobi_buffer_free_null(buf);
    return MOBI_SUCCESS;
}


MOBI_RET mobi_parse_mobiheader(MOBIData *m, MOBIBuffer *buf) {
    int isKF8 = 0;
    if (m == NULL) {
        debug_print("%s", "Mobi structure not initialized\n");
        return MOBI_INIT_FAILED;
    }
    m->mh = calloc(1, sizeof(MOBIMobiHeader));
    if (m->mh == NULL) {
        debug_print("%s", "Memory allocation for MOBI header failed\n");
        return MOBI_MALLOC_FAILED;
    }
    mobi_buffer_getstring(m->mh->mobi_magic, buf, 4);
    mobi_buffer_dup32(&m->mh->header_length, buf);
    if (strcmp(m->mh->mobi_magic, MOBI_MAGIC) != 0 || m->mh->header_length == NULL) {
        debug_print("%s", "MOBI header not found\n");
        mobi_free_mh(m->mh);
        m->mh = NULL;
        return MOBI_DATA_CORRUPT;
    }
    const size_t saved_maxlen = buf->maxlen;
    /* some old files declare zero length mobi header, try to read first 24 bytes anyway */
    uint32_t header_length = (*m->mh->header_length > 0) ? *m->mh->header_length : 24;
    /* read only declared MOBI header length (curr offset minus 8 already read bytes) */
    const size_t left_length = header_length + buf->offset - 8;
    buf->maxlen = saved_maxlen < left_length ? saved_maxlen : left_length;
    mobi_buffer_dup32(&m->mh->mobi_type, buf);
    uint32_t encoding = mobi_buffer_get32(buf);
    if (encoding == 1252) {
        m->mh->text_encoding = malloc(sizeof(MOBIEncoding));
        if (m->mh->text_encoding == NULL) {
            debug_print("%s", "Memory allocation for MOBI header failed\n");
            return MOBI_MALLOC_FAILED;
        }
        *m->mh->text_encoding = MOBI_CP1252;
    }
    else if (encoding == 65001) {
        m->mh->text_encoding = malloc(sizeof(MOBIEncoding));
        if (m->mh->text_encoding == NULL) {
            debug_print("%s", "Memory allocation for MOBI header failed\n");
            return MOBI_MALLOC_FAILED;
        }
        *m->mh->text_encoding = MOBI_UTF8;
    } else {
        debug_print("Unknown encoding in mobi header: %i\n", encoding);
    }
    mobi_buffer_dup32(&m->mh->uid, buf);
    mobi_buffer_dup32(&m->mh->version, buf);
    if (header_length >= MOBI_HEADER_V7_SIZE
        && m->mh->version && *m->mh->version == 8) {
        isKF8 = 1;
    }
    mobi_buffer_dup32(&m->mh->orth_index, buf);
    mobi_buffer_dup32(&m->mh->infl_index, buf);
    mobi_buffer_dup32(&m->mh->names_index, buf);
    mobi_buffer_dup32(&m->mh->keys_index, buf);
    mobi_buffer_dup32(&m->mh->extra0_index, buf);
    mobi_buffer_dup32(&m->mh->extra1_index, buf);
    mobi_buffer_dup32(&m->mh->extra2_index, buf);
    mobi_buffer_dup32(&m->mh->extra3_index, buf);
    mobi_buffer_dup32(&m->mh->extra4_index, buf);
    mobi_buffer_dup32(&m->mh->extra5_index, buf);
    mobi_buffer_dup32(&m->mh->non_text_index, buf);
    mobi_buffer_dup32(&m->mh->full_name_offset, buf);
    mobi_buffer_dup32(&m->mh->full_name_length, buf);
    mobi_buffer_dup32(&m->mh->locale, buf);
    mobi_buffer_dup32(&m->mh->dict_input_lang, buf);
    mobi_buffer_dup32(&m->mh->dict_output_lang, buf);
    mobi_buffer_dup32(&m->mh->min_version, buf);
    mobi_buffer_dup32(&m->mh->image_index, buf);
    mobi_buffer_dup32(&m->mh->huff_rec_index, buf);
    mobi_buffer_dup32(&m->mh->huff_rec_count, buf);
    mobi_buffer_dup32(&m->mh->datp_rec_index, buf);
    mobi_buffer_dup32(&m->mh->datp_rec_count, buf);
    mobi_buffer_dup32(&m->mh->exth_flags, buf);
    mobi_buffer_seek(buf, 32); /* 32 unknown bytes */
    mobi_buffer_dup32(&m->mh->unknown6, buf);
    mobi_buffer_dup32(&m->mh->drm_offset, buf);
    mobi_buffer_dup32(&m->mh->drm_count, buf);
    mobi_buffer_dup32(&m->mh->drm_size, buf);
    mobi_buffer_dup32(&m->mh->drm_flags, buf);
    mobi_buffer_seek(buf, 8); /* 8 unknown bytes */
    if (isKF8) {
        mobi_buffer_dup32(&m->mh->fdst_index, buf);
    } else {
        mobi_buffer_dup16(&m->mh->first_text_index, buf);
        mobi_buffer_dup16(&m->mh->last_text_index, buf);
    }
    mobi_buffer_dup32(&m->mh->fdst_section_count, buf);
    mobi_buffer_dup32(&m->mh->fcis_index, buf);
    mobi_buffer_dup32(&m->mh->fcis_count, buf);
    mobi_buffer_dup32(&m->mh->flis_index, buf);
    mobi_buffer_dup32(&m->mh->flis_count, buf);
    mobi_buffer_dup32(&m->mh->unknown10, buf);
    mobi_buffer_dup32(&m->mh->unknown11, buf);
    mobi_buffer_dup32(&m->mh->srcs_index, buf);
    mobi_buffer_dup32(&m->mh->srcs_count, buf);
    mobi_buffer_dup32(&m->mh->unknown12, buf);
    mobi_buffer_dup32(&m->mh->unknown13, buf);
    mobi_buffer_seek(buf, 2); /* 2 byte fill */
    mobi_buffer_dup16(&m->mh->extra_flags, buf);
    mobi_buffer_dup32(&m->mh->ncx_index, buf);
    if (isKF8) {
        mobi_buffer_dup32(&m->mh->fragment_index, buf);
        mobi_buffer_dup32(&m->mh->skeleton_index, buf);
    } else {
        mobi_buffer_dup32(&m->mh->unknown14, buf);
        mobi_buffer_dup32(&m->mh->unknown15, buf);
    }
    mobi_buffer_dup32(&m->mh->datp_index, buf);
    if (isKF8) {
        mobi_buffer_dup32(&m->mh->guide_index, buf);
    } else {
        mobi_buffer_dup32(&m->mh->unknown16, buf);
    }
    mobi_buffer_dup32(&m->mh->unknown17, buf);
    mobi_buffer_dup32(&m->mh->unknown18, buf);
    mobi_buffer_dup32(&m->mh->unknown19, buf);
    mobi_buffer_dup32(&m->mh->unknown20, buf);
    if (buf->maxlen > buf->offset) {
        debug_print("Skipping %zu unknown bytes in MOBI header\n", (buf->maxlen - buf->offset));
        mobi_buffer_setpos(buf, buf->maxlen);
    }
    buf->maxlen = saved_maxlen;
    /* get full name stored at m->mh->full_name_offset */
    if (m->mh->full_name_offset && m->mh->full_name_length) {
        const size_t saved_offset = buf->offset;
        const uint32_t full_name_length = min(*m->mh->full_name_length, MOBI_TITLE_SIZEMAX);
        mobi_buffer_setpos(buf, *m->mh->full_name_offset);
        m->mh->full_name = malloc(full_name_length + 1);
        if (m->mh->full_name == NULL) {
            debug_print("%s", "Memory allocation for full name failed\n");
            return MOBI_MALLOC_FAILED;
        }
        if (full_name_length) {
            mobi_buffer_getstring(m->mh->full_name, buf, full_name_length);
        } else {
            m->mh->full_name[0] = '\0';
        }
        mobi_buffer_setpos(buf, saved_offset);
    }
    return MOBI_SUCCESS;
}


glob_dir_to_array (dir, array, flags)
     char *dir, **array;
     int flags;
{
  register unsigned int i, l;
  int add_slash;
  char **result, *new;
  struct stat sb;

  l = strlen (dir);
  if (l == 0)
    {
      if (flags & GX_MARKDIRS)
	for (i = 0; array[i]; i++)
	  {
	    if ((stat (array[i], &sb) == 0) && S_ISDIR (sb.st_mode))
	      {
		l = strlen (array[i]);
		new = (char *)realloc (array[i], l + 2);
		if (new == 0)
		  return NULL;
		new[l] = '/';
		new[l+1] = '\0';
		array[i] = new;
	      }
	  }
      return (array);
    }

  add_slash = dir[l - 1] != '/';

  i = 0;
  while (array[i] != NULL)
    ++i;

  result = (char **) malloc ((i + 1) * sizeof (char *));
  if (result == NULL)
    return (NULL);

  for (i = 0; array[i] != NULL; i++)
    {
      /* 3 == 1 for NUL, 1 for slash at end of DIR, 1 for GX_MARKDIRS */
      result[i] = (char *) malloc (l + strlen (array[i]) + 3);

      if (result[i] == NULL)
	{
	  int ind;
	  for (ind = 0; ind < i; ind++)
	    free (result[ind]);
	  free (result);
	  return (NULL);
	}

      strcpy (result[i], dir);
      if (add_slash)
	result[i][l] = '/';
      if (array[i][0])
	{
	  strcpy (result[i] + l + add_slash, array[i]);
	  if (flags & GX_MARKDIRS)
	    {
	      if ((stat (result[i], &sb) == 0) && S_ISDIR (sb.st_mode))
		{
		  size_t rlen;
		  rlen = strlen (result[i]);
		  result[i][rlen] = '/';
		  result[i][rlen+1] = '\0';
		}
	    }
	}
      else
        result[i][l+add_slash] = '\0';
    }
  result[i] = NULL;

  /* Free the input array.  */
  for (i = 0; array[i] != NULL; i++)
    free (array[i]);
  free ((char *) array);

  return (result);
}

An issue was discovered in disable_priv_mode in shell.c in GNU Bash through 5.0 patch 11. By default, if Bash is run with its effective UID not equal to its real UID, it will drop privileges by setting its effective UID to its real UID. However, it does so incorrectly. On Linux and other systems that support "saved UID" functionality, the saved UID is not dropped. An attacker with command execution in the shell can use "enable -f" for runtime loading of a new builtin, which can be a shared object that calls setuid() and therefore regains privileges. However, binaries running with an effective UID of 0 are unaffected.


glob_pattern_p (pattern)
     const char *pattern;
{
#if HANDLE_MULTIBYTE
  size_t n;
  wchar_t *wpattern;
  int r;

  if (MB_CUR_MAX == 1 || mbsmbchar (pattern) == 0)
    return (internal_glob_pattern_p ((unsigned char *)pattern));

  /* Convert strings to wide chars, and call the multibyte version. */
  n = xdupmbstowcs (&wpattern, NULL, pattern);
  if (n == (size_t)-1)
    /* Oops.  Invalid multibyte sequence.  Try it as single-byte sequence. */
    return (internal_glob_pattern_p ((unsigned char *)pattern));

  r = internal_glob_wpattern_p (wpattern);
  free (wpattern);

  return r;
#else
  return (internal_glob_pattern_p ((unsigned char *)pattern));
#endif
}


set_shell_name (argv0)
     char *argv0;
{
  /* Here's a hack.  If the name of this shell is "sh", then don't do
     any startup files; just try to be more like /bin/sh. */
  shell_name = argv0 ? base_pathname (argv0) : PROGRAM;

  if (argv0 && *argv0 == '-')
    {
      if (*shell_name == '-')
	shell_name++;
      login_shell = 1;
    }

  if (shell_name[0] == 's' && shell_name[1] == 'h' && shell_name[2] == '\0')
    act_like_sh++;
  if (shell_name[0] == 's' && shell_name[1] == 'u' && shell_name[2] == '\0')
    su_shell++;

  shell_name = argv0 ? argv0 : PROGRAM;
  FREE (dollar_vars[0]);
  dollar_vars[0] = savestring (shell_name);

  /* A program may start an interactive shell with
	  "execl ("/bin/bash", "-", NULL)".
     If so, default the name of this shell to our name. */
  if (!shell_name || !*shell_name || (shell_name[0] == '-' && !shell_name[1]))
    shell_name = PROGRAM;
}


posix_readline_initialize (on_or_off)
     int on_or_off;
{
  static char kseq[2] = { CTRL ('I'), 0 };		/* TAB */

  if (on_or_off)
    rl_variable_bind ("comment-begin", "#");
#if defined (VI_MODE)
  if (on_or_off)
    {
      vi_tab_binding = rl_function_of_keyseq (kseq, vi_insertion_keymap, (int *)NULL);
      rl_bind_key_in_map (CTRL ('I'), rl_insert, vi_insertion_keymap);
    }
  else
    {
      if (rl_function_of_keyseq (kseq, vi_insertion_keymap, (int *)NULL) == rl_insert)
        rl_bind_key_in_map (CTRL ('I'), vi_tab_binding, vi_insertion_keymap);
    }
#endif
}


main (argc, argv)
     int argc;
     char **argv;
{
  unsigned int i;

  for (i = 1; i < argc; ++i)
    {
      char **value = glob_filename (argv[i], 0);
      if (value == NULL)
	puts ("Out of memory.");
      else if (value == &glob_error_return)
	perror (argv[i]);
      else
	for (i = 0; value[i] != NULL; i++)
	  puts (value[i]);
    }

  exit (0);
}


bind_args (argv, arg_start, arg_end, start_index)
     char **argv;
     int arg_start, arg_end, start_index;
{
  register int i;
  WORD_LIST *args, *tl;

  for (i = arg_start, args = tl = (WORD_LIST *)NULL; i < arg_end; i++)
    {
      if (args == 0)
	args = tl = make_word_list (make_word (argv[i]), args);
      else
	{
	  tl->next = make_word_list (make_word (argv[i]), (WORD_LIST *)NULL);
	  tl = tl->next;
	}
    }

  if (args)
    {
      if (start_index == 0)	/* bind to $0...$n for sh -c command */
	{
	  /* Posix.2 4.56.3 says that the first argument after sh -c command
	     becomes $0, and the rest of the arguments become $1...$n */
	  shell_name = savestring (args->word->word);
	  FREE (dollar_vars[0]);
	  dollar_vars[0] = savestring (args->word->word);
	  remember_args (args->next, 1);
	  if (debugging_mode)
	    {
	      push_args (args->next);	/* BASH_ARGV and BASH_ARGC */
	      bash_argv_initialized = 1;
	    }
	}
      else			/* bind to $1...$n for shell script */
        {
	  remember_args (args, 1);
	  /* We do this unconditionally so something like -O extdebug doesn't
	     do it first.  We're setting the definitive positional params
	     here. */
	  if (debugging_mode)
	    {
	      push_args (args);		/* BASH_ARGV and BASH_ARGC */
	      bash_argv_initialized = 1;
	    }
        }

      dispose_words (args);
    }

  return (i);
}


test_for_canon_directory (name)
     const char *name;
{
  char *fn;
  int r;

  fn = (*name == '~') ? bash_tilde_expand (name, 0) : savestring (name);
  bash_filename_stat_hook (&fn);
  r = file_isdir (fn);
  free (fn);

  return (r);
}


subshell_exit (s)
     int s;
{
  fflush (stdout);
  fflush (stderr);

  /* Do trap[0] if defined.  Allow it to override the exit status
     passed to us. */
  if (signal_is_trapped (0))
    s = run_exit_trap ();

  sh_exit (s);
}


open_shell_script (script_name)
     char *script_name;
{
  int fd, e, fd_is_tty;
  char *filename, *path_filename, *t;
  char sample[80];
  int sample_len;
  struct stat sb;
#if defined (ARRAY_VARS)
  SHELL_VAR *funcname_v, *bash_source_v, *bash_lineno_v;
  ARRAY *funcname_a, *bash_source_a, *bash_lineno_a;
#endif

  filename = savestring (script_name);

  fd = open (filename, O_RDONLY);
  if ((fd < 0) && (errno == ENOENT) && (absolute_program (filename) == 0))
    {
      e = errno;
      /* If it's not in the current directory, try looking through PATH
	 for it. */
      path_filename = find_path_file (script_name);
      if (path_filename)
	{
	  free (filename);
	  filename = path_filename;
	  fd = open (filename, O_RDONLY);
	}
      else
	errno = e;
    }

  if (fd < 0)
    {
      e = errno;
      file_error (filename);
#if defined (JOB_CONTROL)
      end_job_control ();	/* just in case we were run as bash -i script */
#endif
      sh_exit ((e == ENOENT) ? EX_NOTFOUND : EX_NOINPUT);
    }

  free (dollar_vars[0]);
  dollar_vars[0] = exec_argv0 ? savestring (exec_argv0) : savestring (script_name);
  if (exec_argv0)
    {
      free (exec_argv0);
      exec_argv0 = (char *)NULL;
    }

  if (file_isdir (filename))
    {
#if defined (EISDIR)
      errno = EISDIR;
#else
      errno = EINVAL;
#endif
      file_error (filename);
#if defined (JOB_CONTROL)
      end_job_control ();	/* just in case we were run as bash -i script */
#endif
      sh_exit (EX_NOINPUT);
    }

#if defined (ARRAY_VARS)
  GET_ARRAY_FROM_VAR ("FUNCNAME", funcname_v, funcname_a);
  GET_ARRAY_FROM_VAR ("BASH_SOURCE", bash_source_v, bash_source_a);
  GET_ARRAY_FROM_VAR ("BASH_LINENO", bash_lineno_v, bash_lineno_a);

  array_push (bash_source_a, filename);
  if (bash_lineno_a)
    {
      t = itos (executing_line_number ());
      array_push (bash_lineno_a, t);
      free (t);
    }
  array_push (funcname_a, "main");
#endif

#ifdef HAVE_DEV_FD
  fd_is_tty = isatty (fd);
#else
  fd_is_tty = 0;
#endif

  /* Only do this with non-tty file descriptors we can seek on. */
  if (fd_is_tty == 0 && (lseek (fd, 0L, 1) != -1))
    {
      /* Check to see if the `file' in `bash file' is a binary file
	 according to the same tests done by execute_simple_command (),
	 and report an error and exit if it is. */
      sample_len = read (fd, sample, sizeof (sample));
      if (sample_len < 0)
	{
	  e = errno;
	  if ((fstat (fd, &sb) == 0) && S_ISDIR (sb.st_mode))
	    {
#if defined (EISDIR)
	      errno = EISDIR;
	      file_error (filename);
#else
	      internal_error (_("%s: Is a directory"), filename);
#endif
	    }
	  else
	    {
	      errno = e;
	      file_error (filename);
	    }
#if defined (JOB_CONTROL)
	  end_job_control ();	/* just in case we were run as bash -i script */
#endif
	  exit (EX_NOEXEC);
	}
      else if (sample_len > 0 && (check_binary_file (sample, sample_len)))
	{
	  internal_error (_("%s: cannot execute binary file"), filename);
#if defined (JOB_CONTROL)
	  end_job_control ();	/* just in case we were run as bash -i script */
#endif
	  exit (EX_BINARY_FILE);
	}
      /* Now rewind the file back to the beginning. */
      lseek (fd, 0L, 0);
    }

  /* Open the script.  But try to move the file descriptor to a randomly
     large one, in the hopes that any descriptors used by the script will
     not match with ours. */
  fd = move_to_high_fd (fd, 1, -1);

#if defined (BUFFERED_INPUT)
  default_buffered_input = fd;
  SET_CLOSE_ON_EXEC (default_buffered_input);
#else /* !BUFFERED_INPUT */
  default_input = fdopen (fd, "r");

  if (default_input == 0)
    {
      file_error (filename);
      exit (EX_NOTFOUND);
    }

  SET_CLOSE_ON_EXEC (fd);
  if (fileno (default_input) != fd)
    SET_CLOSE_ON_EXEC (fileno (default_input));
#endif /* !BUFFERED_INPUT */

  /* Just about the only way for this code to be executed is if something
     like `bash -i /dev/stdin' is executed. */
  if (interactive_shell && fd_is_tty)
    {
      dup2 (fd, 0);
      close (fd);
      fd = 0;
#if defined (BUFFERED_INPUT)
      default_buffered_input = 0;
#else
      fclose (default_input);
      default_input = stdin;
#endif
    }
  else if (forced_interactive && fd_is_tty == 0)
    /* But if a script is called with something like `bash -i scriptname',
       we need to do a non-interactive setup here, since we didn't do it
       before. */
    init_interactive_script ();

  free (filename);

  reading_shell_script = 1;
  return (fd);
}


bash_backward_shellword (count, key)
     int count, key;
{
  size_t slen;
  int c, p, prev_p;
  DECLARE_MBSTATE;

  if (count < 0)
    return (bash_forward_shellword (-count, key));

  p = rl_point;
  slen = rl_end;

  while (count)
    {
      if (p == 0)
	{
	  rl_point = 0;
	  return 0;
	}

      /* Move backward until we hit a non-metacharacter. We want to deal
	 with the characters before point, so we move off a word if we're
	 at its first character. */
      BACKUP_CHAR (rl_line_buffer, slen, p);
      while (p > 0)
	{
	  c = rl_line_buffer[p];
	  if (WORDDELIM (c) == 0 || char_is_quoted (rl_line_buffer, p))
	    break;
	  BACKUP_CHAR (rl_line_buffer, slen, p);
	}

      if (p == 0)
	{
	  rl_point = 0;
	  return 0;
	}

      /* Now move backward until we hit a metacharacter or BOL. Leave point
	 at the start of the shellword or at BOL. */
      prev_p = p;
      while (p > 0)
	{
	  c = rl_line_buffer[p];
	  if (WORDDELIM (c) && char_is_quoted (rl_line_buffer, p) == 0)
	    {
	      p = prev_p;
	      break;
	    }
	  prev_p = p;
	  BACKUP_CHAR (rl_line_buffer, slen, p);
	}

      count--;
    }

  rl_point = p;
  return 0;
}


shell_is_restricted (name)
     char *name;
{
  char *temp;

  if (restricted)
    return 1;
  temp = base_pathname (name);
  if (*temp == '-')
    temp++;
  return (STREQ (temp, RESTRICTED_SHELL_NAME));
}


mbskipname (pat, dname, flags)
     char *pat, *dname;
     int flags;
{
  int ret, ext;
  wchar_t *pat_wc, *dn_wc;
  size_t pat_n, dn_n;

  if (mbsmbchar (dname) == 0 && mbsmbchar (pat) == 0)
    return (skipname (pat, dname, flags));

  ext = 0;
#if EXTENDED_GLOB
  ext = extglob_pattern_p (pat);
#endif

  pat_wc = dn_wc = (wchar_t *)NULL;

  pat_n = xdupmbstowcs (&pat_wc, NULL, pat);
  if (pat_n != (size_t)-1)
    dn_n = xdupmbstowcs (&dn_wc, NULL, dname);

  ret = 0;
  if (pat_n != (size_t)-1 && dn_n !=(size_t)-1)
    ret = ext ? wextglob_skipname (pat_wc, dn_wc, flags) : wskipname (pat_wc, dn_wc, flags);
  else
    ret = skipname (pat, dname, flags);

  FREE (pat_wc);
  FREE (dn_wc);

  return ret;
}


ere_char (c)
     int c;
{
  switch (c)
    {
    case '.':
    case '[':
    case '\\':
    case '(':
    case ')':
    case '*':
    case '+':
    case '?':
    case '{':
    case '|':
    case '^':
    case '$':
      return 1;
    default:
      return 0;
    }
  return (0);
}


variable_completion_function (text, state)
     const char *text;
     int state;
{
  static char **varlist = (char **)NULL;
  static int varlist_index;
  static char *varname = (char *)NULL;
  static int first_char, first_char_loc;

  if (!state)
    {
      if (varname)
	free (varname);

      first_char_loc = 0;
      first_char = text[0];

      if (first_char == '$')
	first_char_loc++;

      if (text[first_char_loc] == '{')
	first_char_loc++;

      varname = savestring (text + first_char_loc);

      if (varlist)
	strvec_dispose (varlist);

      varlist = all_variables_matching_prefix (varname);
      varlist_index = 0;
    }

  if (!varlist || !varlist[varlist_index])
    {
      return ((char *)NULL);
    }
  else
    {
      char *value;

      value = (char *)xmalloc (4 + strlen (varlist[varlist_index]));

      if (first_char_loc)
	{
	  value[0] = first_char;
	  if (first_char_loc == 2)
	    value[1] = '{';
	}

      strcpy (value + first_char_loc, varlist[varlist_index]);
      if (first_char_loc == 2)
	strcat (value, "}");

      varlist_index++;
      return (value);
    }
}


skipname (pat, dname, flags)
     char *pat;
     char *dname;
     int flags;
{
#if EXTENDED_GLOB
  if (extglob_pattern_p (pat))		/* XXX */
    return (extglob_skipname (pat, dname, flags));
#endif

  if (glob_always_skip_dot_and_dotdot && DOT_OR_DOTDOT (dname))
    return 1;

  /* If a leading dot need not be explicitly matched, and the pattern
     doesn't start with a `.', don't match `.' or `..' */
  if (noglob_dot_filenames == 0 && pat[0] != '.' &&
	(pat[0] != '\\' || pat[1] != '.') &&
	DOT_OR_DOTDOT (dname))
    return 1;

  /* If a dot must be explicitly matched, check to see if they do. */
  else if (noglob_dot_filenames && dname[0] == '.' && pat[0] != '.' &&
	(pat[0] != '\\' || pat[1] != '.'))
    return 1;

  return 0;
}


run_wordexp (words)
     char *words;
{
  int code, nw, nb;
  WORD_LIST *wl, *tl, *result;

  code = setjmp_nosigs (top_level);

  if (code != NOT_JUMPED)
    {
      switch (code)
	{
	  /* Some kind of throw to top_level has occurred. */
	case FORCE_EOF:
	  return last_command_exit_value = 127;
	case ERREXIT:
	case EXITPROG:
	  return last_command_exit_value;
	case DISCARD:
	  return last_command_exit_value = 1;
	default:
	  command_error ("run_wordexp", CMDERR_BADJUMP, code, 0);
	}
    }

  /* Run it through the parser to get a list of words and expand them */
  if (words && *words)
    {
      with_input_from_string (words, "--wordexp");
      if (parse_command () != 0)
	return (126);
      if (global_command == 0)
	{
	  printf ("0\n0\n");
	  return (0);
	}
      if (global_command->type != cm_simple)
	return (126);
      wl = global_command->value.Simple->words;
      if (protected_mode)
	for (tl = wl; tl; tl = tl->next)
	  tl->word->flags |= W_NOCOMSUB|W_NOPROCSUB;
      result = wl ? expand_words_no_vars (wl) : (WORD_LIST *)0;
    }
  else
    result = (WORD_LIST *)0;

  last_command_exit_value = 0;

  if (result == 0)
    {
      printf ("0\n0\n");
      return (0);
    }

  /* Count up the number of words and bytes, and print them.  Don't count
     the trailing NUL byte. */
  for (nw = nb = 0, wl = result; wl; wl = wl->next)
    {
      nw++;
      nb += strlen (wl->word->word);
    }
  printf ("%u\n%u\n", nw, nb);
  /* Print each word on a separate line.  This will have to be changed when
     the interface to glibc is completed. */
  for (wl = result; wl; wl = wl->next)
    printf ("%s\n", wl->word->word);

  return (0);
}


sh_exit (s)
     int s;
{
#if defined (MALLOC_DEBUG) && defined (USING_BASH_MALLOC)
  if (malloc_trace_at_exit && (subshell_environment & (SUBSHELL_COMSUB|SUBSHELL_PROCSUB)) == 0)
    trace_malloc_stats (get_name_for_error (), (char *)NULL);
  /* mlocation_write_table (); */
#endif

  exit (s);
}


finddirs (pat, sdir, flags, ep, np)
     char *pat;
     char *sdir;
     int flags;
     struct globval **ep;
     int *np;
{
  char **r, *n;
  int ndirs;
  struct globval *ret, *e, *g;

/*itrace("finddirs: pat = `%s' sdir = `%s' flags = 0x%x", pat, sdir, flags);*/
  e = ret = 0;
  r = glob_vector (pat, sdir, flags);
  if (r == 0 || r[0] == 0)
    {
      if (np)
	*np = 0;
      if (ep)
        *ep = 0;
      if (r && r != &glob_error_return)
	free (r);
      return (struct globval *)0;
    }
  for (ndirs = 0; r[ndirs] != 0; ndirs++)
    {
      g = (struct globval *) malloc (sizeof (struct globval));
      if (g == 0)
	{
	  while (ret)		/* free list built so far */
	    {
	      g = ret->next;
	      free (ret);
	      ret = g;
	    }

	  free (r);
	  if (np)
	    *np = 0;
	  if (ep)
	    *ep = 0;
	  return (&finddirs_error_return);
	}
      if (e == 0)
	e = g;

      g->next = ret;
      ret = g;

      g->name = r[ndirs];
    }

  free (r);
  if (ep)
    *ep = e;
  if (np)
    *np = ndirs;

  return ret;
}


init_noninteractive ()
{
#if defined (HISTORY)
  bash_history_reinit (0);
#endif /* HISTORY */
  interactive_shell = startup_state = interactive = 0;
  expand_aliases = posixly_correct;	/* XXX - was 0 not posixly_correct */
  no_line_editing = 1;
#if defined (JOB_CONTROL)
  /* Even if the shell is not interactive, enable job control if the -i or
     -m option is supplied at startup. */
  set_job_control (forced_interactive||jobs_m_flag);
#endif /* JOB_CONTROL */
}


setup_glob_ignore (name)
     char *name;
{
  char *v;

  v = get_string_value (name);
  setup_ignore_patterns (&globignore);

  if (globignore.num_ignores)
    glob_dot_filenames = 1;
  else if (v == 0)
    glob_dot_filenames = 0;
}


wextglob_skipname (pat, dname, flags)
     wchar_t *pat, *dname;
     int flags;
{
#if EXTENDED_GLOB
  wchar_t *pp, *pe, *t, n, *se;
  int r, negate, wild, nullpat;

  negate = *pat == L'!';
  wild = *pat == L'*' || *pat == L'?';
  pp = pat + 2;
  se = pp + wcslen (pp) - 1;	/*(*/
  pe = glob_patscan_wc (pp, se, 0);

  if (pe == se && *pe == ')' && (t = wcschr (pp, L'|')) == 0)
    {
      *pe = L'\0';
      r = wskipname (pp, dname, flags); /*(*/
      *pe = L')';
      if (wild && pe[1] != L'\0')
        return (wskipname (pe+1, dname, flags));
      return r;
    }

  /* Is the extglob pattern between the parens the null pattern?  The null
     pattern can match nothing, so should we check any remaining portion of
     the pattern? */
  nullpat = pe >= (pat + 2) && pe[-2] == L'(' && pe[-1] == L')';

  /* check every subpattern */
  while (t = glob_patscan_wc (pp, pe, '|'))
    {
      n = t[-1];	/* ( */
      if (wextglob_pattern_p (pp) && n == L')')
	t[-1] = n;	/* no-op for now */
      else
	t[-1] = L'\0';
      r = wskipname (pp, dname, flags);
      t[-1] = n;
      if (r == 0)
	return 0;
      pp = t;
    }

  if (pp == pe)		/* glob_patscan_wc might find end of pattern */
    return r;

  /* but if it doesn't then we didn't match a leading dot */
  if (wild && *pe != L'\0')
    return (wskipname (pe, dname, flags));
  return 1;
#else
  return (wskipname (pat, dname, flags));
#endif
}


get_cmd_xmap_from_edit_mode ()
{
  if (emacs_std_cmd_xmap == 0)
    init_unix_command_map ();

  switch (rl_editing_mode)
    {
    case EMACS_EDITING_MODE:
      return emacs_std_cmd_xmap;
#if defined (VI_MODE)
    case VI_EDITING_MODE:
      return (get_cmd_xmap_from_keymap (rl_get_keymap ()));
#endif
    default:
      return (Keymap)NULL;
    }
}


exit_shell (s)
     int s;
{
  fflush (stdout);		/* XXX */
  fflush (stderr);

  /* Clean up the terminal if we are in a state where it's been modified. */
#if defined (READLINE)
  if (RL_ISSTATE (RL_STATE_TERMPREPPED) && rl_deprep_term_function)
    (*rl_deprep_term_function) ();
#endif
  if (read_tty_modified ())
    read_tty_cleanup ();

  /* Do trap[0] if defined.  Allow it to override the exit status
     passed to us. */
  if (signal_is_trapped (0))
    s = run_exit_trap ();

#if defined (PROCESS_SUBSTITUTION)
  unlink_fifo_list ();
#endif /* PROCESS_SUBSTITUTION */

#if defined (HISTORY)
  if (remember_on_history)
    maybe_save_shell_history ();
#endif /* HISTORY */

#if defined (COPROCESS_SUPPORT)
  coproc_flush ();
#endif

#if defined (JOB_CONTROL)
  /* If the user has run `shopt -s huponexit', hangup all jobs when we exit
     an interactive login shell.  ksh does this unconditionally. */
  if (interactive_shell && login_shell && hup_on_exit)
    hangup_all_jobs ();

  /* If this shell is interactive, or job control is active, terminate all
     stopped jobs and restore the original terminal process group.  Don't do
     this if we're in a subshell and calling exit_shell after, for example,
     a failed word expansion.  We want to do this even if the shell is not
     interactive because we set the terminal's process group when job control
     is enabled regardless of the interactive status. */
  if (subshell_environment == 0)
    end_job_control ();
#endif /* JOB_CONTROL */

  /* Always return the exit status of the last command to our parent. */
  sh_exit (s);
}


run_startup_files ()
{
#if defined (JOB_CONTROL)
  int old_job_control;
#endif
  int sourced_login, run_by_ssh;

  /* get the rshd/sshd case out of the way first. */
  if (interactive_shell == 0 && no_rc == 0 && login_shell == 0 &&
      act_like_sh == 0 && command_execution_string)
    {
#ifdef SSH_SOURCE_BASHRC
      run_by_ssh = (find_variable ("SSH_CLIENT") != (SHELL_VAR *)0) ||
		   (find_variable ("SSH2_CLIENT") != (SHELL_VAR *)0);
#else
      run_by_ssh = 0;
#endif

      /* If we were run by sshd or we think we were run by rshd, execute
	 ~/.bashrc if we are a top-level shell. */
      if ((run_by_ssh || isnetconn (fileno (stdin))) && shell_level < 2)
	{
#ifdef SYS_BASHRC
#  if defined (__OPENNT)
	  maybe_execute_file (_prefixInstallPath(SYS_BASHRC, NULL, 0), 1);
#  else
	  maybe_execute_file (SYS_BASHRC, 1);
#  endif
#endif
	  maybe_execute_file (bashrc_file, 1);
	  return;
	}
    }

#if defined (JOB_CONTROL)
  /* Startup files should be run without job control enabled. */
  old_job_control = interactive_shell ? set_job_control (0) : 0;
#endif

  sourced_login = 0;

  /* A shell begun with the --login (or -l) flag that is not in posix mode
     runs the login shell startup files, no matter whether or not it is
     interactive.  If NON_INTERACTIVE_LOGIN_SHELLS is defined, run the
     startup files if argv[0][0] == '-' as well. */
#if defined (NON_INTERACTIVE_LOGIN_SHELLS)
  if (login_shell && posixly_correct == 0)
#else
  if (login_shell < 0 && posixly_correct == 0)
#endif
    {
      /* We don't execute .bashrc for login shells. */
      no_rc++;

      /* Execute /etc/profile and one of the personal login shell
	 initialization files. */
      if (no_profile == 0)
	{
	  maybe_execute_file (SYS_PROFILE, 1);

	  if (act_like_sh)	/* sh */
	    maybe_execute_file ("~/.profile", 1);
	  else if ((maybe_execute_file ("~/.bash_profile", 1) == 0) &&
		   (maybe_execute_file ("~/.bash_login", 1) == 0))	/* bash */
	    maybe_execute_file ("~/.profile", 1);
	}

      sourced_login = 1;
    }

  /* A non-interactive shell not named `sh' and not in posix mode reads and
     executes commands from $BASH_ENV.  If `su' starts a shell with `-c cmd'
     and `-su' as the name of the shell, we want to read the startup files.
     No other non-interactive shells read any startup files. */
  if (interactive_shell == 0 && !(su_shell && login_shell))
    {
      if (posixly_correct == 0 && act_like_sh == 0 && privileged_mode == 0 &&
	    sourced_env++ == 0)
	execute_env_file (get_string_value ("BASH_ENV"));
      return;
    }

  /* Interactive shell or `-su' shell. */
  if (posixly_correct == 0)		  /* bash, sh */
    {
      if (login_shell && sourced_login++ == 0)
	{
	  /* We don't execute .bashrc for login shells. */
	  no_rc++;

	  /* Execute /etc/profile and one of the personal login shell
	     initialization files. */
	  if (no_profile == 0)
	    {
	      maybe_execute_file (SYS_PROFILE, 1);

	      if (act_like_sh)	/* sh */
		maybe_execute_file ("~/.profile", 1);
	      else if ((maybe_execute_file ("~/.bash_profile", 1) == 0) &&
		       (maybe_execute_file ("~/.bash_login", 1) == 0))	/* bash */
		maybe_execute_file ("~/.profile", 1);
	    }
	}

      /* bash */
      if (act_like_sh == 0 && no_rc == 0)
	{
#ifdef SYS_BASHRC
#  if defined (__OPENNT)
	  maybe_execute_file (_prefixInstallPath(SYS_BASHRC, NULL, 0), 1);
#  else
	  maybe_execute_file (SYS_BASHRC, 1);
#  endif
#endif
	  maybe_execute_file (bashrc_file, 1);
	}
      /* sh */
      else if (act_like_sh && privileged_mode == 0 && sourced_env++ == 0)
	execute_env_file (get_string_value ("ENV"));
    }
  else		/* bash --posix, sh --posix */
    {
      /* bash and sh */
      if (interactive_shell && privileged_mode == 0 && sourced_env++ == 0)
	execute_env_file (get_string_value ("ENV"));
    }

#if defined (JOB_CONTROL)
  set_job_control (old_job_control);
#endif
}


glob_char_p (s)
     const char *s;
{
  switch (*s)
    {
    case '*':
    case '[':
    case ']':
    case '?':
    case '\\':
      return 1;
    case '+':
    case '@':
    case '!':
      if (s[1] == '(')	/*(*/
	return 1;
      break;
    }
  return 0;
}


shell_expand_line (count, ignore)
     int count, ignore;
{
  char *new_line;
  WORD_LIST *expanded_string;
  WORD_DESC *w;

  new_line = 0;
#if defined (BANG_HISTORY)
  new_line = history_expand_line_internal (rl_line_buffer);
#endif

#if defined (ALIAS)
  if (new_line)
    {
      char *alias_line;

      alias_line = alias_expand (new_line);
      free (new_line);
      new_line = alias_line;
    }
#endif /* ALIAS */

  if (new_line)
    {
      int old_point = rl_point;
      int at_end = rl_point == rl_end;

      /* If the line was history and alias expanded, then make that
	 be one thing to undo. */
      maybe_make_readline_line (new_line);
      free (new_line);

      /* If there is variable expansion to perform, do that as a separate
	 operation to be undone. */

#if 1
      w = alloc_word_desc ();
      w->word = savestring (rl_line_buffer);
      w->flags = rl_explicit_arg ? (W_NOPROCSUB|W_NOCOMSUB) : 0;
      expanded_string = expand_word (w, rl_explicit_arg ? Q_HERE_DOCUMENT : 0);
      dispose_word (w);
#else
      new_line = savestring (rl_line_buffer);
      expanded_string = expand_string (new_line, 0);
      FREE (new_line);
#endif

      if (expanded_string == 0)
	{
	  new_line = (char *)xmalloc (1);
	  new_line[0] = '\0';
	}
      else
	{
	  new_line = string_list (expanded_string);
	  dispose_words (expanded_string);
	}

      maybe_make_readline_line (new_line);
      free (new_line);

      /* Place rl_point where we think it should go. */
      if (at_end)
	rl_point = rl_end;
      else if (old_point < rl_end)
	{
	  rl_point = old_point;
	  if (!whitespace (rl_line_buffer[rl_point]))
	    rl_forward_word (1, 0);
	}
      return 0;
    }
  else
    {
      cleanup_expansion_error ();
      return 1;
    }
}


init_unix_command_map ()
{
  emacs_std_cmd_xmap = rl_make_bare_keymap ();

  emacs_std_cmd_xmap[CTRL('X')].type = ISKMAP;
  emacs_std_cmd_xmap[CTRL('X')].function = KEYMAP_TO_FUNCTION (rl_make_bare_keymap ());
  emacs_std_cmd_xmap[ESC].type = ISKMAP;
  emacs_std_cmd_xmap[ESC].function = KEYMAP_TO_FUNCTION (rl_make_bare_keymap ());

#if defined (VI_MODE)
  vi_insert_cmd_xmap = rl_make_bare_keymap ();
  vi_movement_cmd_xmap = rl_make_bare_keymap ();
#endif
}


show_shell_usage (fp, extra)
     FILE *fp;
     int extra;
{
  int i;
  char *set_opts, *s, *t;

  if (extra)
    fprintf (fp, _("GNU bash, version %s-(%s)\n"), shell_version_string (), MACHTYPE);
  fprintf (fp, _("Usage:\t%s [GNU long option] [option] ...\n\t%s [GNU long option] [option] script-file ...\n"),
	     shell_name, shell_name);
  fputs (_("GNU long options:\n"), fp);
  for (i = 0; long_args[i].name; i++)
    fprintf (fp, "\t--%s\n", long_args[i].name);

  fputs (_("Shell options:\n"), fp);
  fputs (_("\t-ilrsD or -c command or -O shopt_option\t\t(invocation only)\n"), fp);

  for (i = 0, set_opts = 0; shell_builtins[i].name; i++)
    if (STREQ (shell_builtins[i].name, "set"))
      {
	set_opts = savestring (shell_builtins[i].short_doc);
	break;
      }

  if (set_opts)
    {
      s = strchr (set_opts, '[');
      if (s == 0)
	s = set_opts;
      while (*++s == '-')
	;
      t = strchr (s, ']');
      if (t)
	*t = '\0';
      fprintf (fp, _("\t-%s or -o option\n"), s);
      free (set_opts);
    }

  if (extra)
    {
      fprintf (fp, _("Type `%s -c \"help set\"' for more information about shell options.\n"), shell_name);
      fprintf (fp, _("Type `%s -c help' for more information about shell builtin commands.\n"), shell_name);
      fprintf (fp, _("Use the `bashbug' command to report bugs.\n"));
      fprintf (fp, "\n");
      fprintf (fp, _("bash home page: <http://www.gnu.org/software/bash>\n"));
      fprintf (fp, _("General help using GNU software: <http://www.gnu.org/gethelp/>\n"));
    }
}


attempt_shell_completion (text, start, end)
     const char *text;
     int start, end;
{
  int in_command_position, ti, qc, dflags;
  char **matches, *command_separator_chars;
#if defined (PROGRAMMABLE_COMPLETION)
  int have_progcomps, was_assignment;
  COMPSPEC *iw_compspec;
#endif

  command_separator_chars = COMMAND_SEPARATORS;
  matches = (char **)NULL;
  rl_ignore_some_completions_function = filename_completion_ignore;

  rl_filename_quote_characters = default_filename_quote_characters;
  set_filename_bstab (rl_filename_quote_characters);
  set_directory_hook ();
  rl_filename_stat_hook = bash_filename_stat_hook;

  rl_sort_completion_matches = 1;	/* sort by default */

  /* Determine if this could be a command word.  It is if it appears at
     the start of the line (ignoring preceding whitespace), or if it
     appears after a character that separates commands.  It cannot be a
     command word if we aren't at the top-level prompt. */
  ti = start - 1;
  qc = -1;

  while ((ti > -1) && (whitespace (rl_line_buffer[ti])))
    ti--;

#if 1
  /* If this is an open quote, maybe we're trying to complete a quoted
     command name. */
  if (ti >= 0 && (rl_line_buffer[ti] == '"' || rl_line_buffer[ti] == '\''))
    {
      qc = rl_line_buffer[ti];
      ti--;
      while (ti > -1 && (whitespace (rl_line_buffer[ti])))
	ti--;
    }
#endif

  in_command_position = 0;
  if (ti < 0)
    {
      /* Only do command completion at the start of a line when we
	 are prompting at the top level. */
      if (current_prompt_string == ps1_prompt)
	in_command_position++;
      else if (parser_in_command_position ())
	in_command_position++;
    }
  else if (member (rl_line_buffer[ti], command_separator_chars))
    {
      in_command_position++;

      if (check_redir (ti) == 1)
	in_command_position = 0;
    }
  else
    {
      /* This still could be in command position.  It is possible
	 that all of the previous words on the line are variable
	 assignments. */
    }

  if (in_command_position && invalid_completion (text, ti))
    {
      rl_attempted_completion_over = 1;
      return ((char **)NULL);
    }

  /* Check that we haven't incorrectly flagged a closed command substitution
     as indicating we're in a command position. */
  if (in_command_position && ti >= 0 && rl_line_buffer[ti] == '`' &&
	*text != '`' && unclosed_pair (rl_line_buffer, end, "`") == 0)
    in_command_position = 0;

  /* Special handling for command substitution.  If *TEXT is a backquote,
     it can be the start or end of an old-style command substitution, or
     unmatched.  If it's unmatched, both calls to unclosed_pair will
     succeed.  Don't bother if readline found a single quote and we are
     completing on the substring.  */
  if (*text == '`' && rl_completion_quote_character != '\'' &&
	(in_command_position || (unclosed_pair (rl_line_buffer, start, "`") &&
				 unclosed_pair (rl_line_buffer, end, "`"))))
    matches = rl_completion_matches (text, command_subst_completion_function);

#if defined (PROGRAMMABLE_COMPLETION)
  /* Attempt programmable completion. */
  have_progcomps = prog_completion_enabled && (progcomp_size () > 0);
  iw_compspec = progcomp_search (INITIALWORD);
  if (matches == 0 &&
      (in_command_position == 0 || text[0] == '\0' || (in_command_position && iw_compspec)) &&
      current_prompt_string == ps1_prompt)
    {
      int s, e, s1, e1, os, foundcs;
      char *n;

      /* XXX - don't free the members */
      if (prog_complete_matches)
	free (prog_complete_matches);
      prog_complete_matches = (char **)NULL;

      os = start;
      n = 0;
      was_assignment = 0;
      s = find_cmd_start (os);
      e = find_cmd_end (end);
      do
	{
	  /* Don't read past the end of rl_line_buffer */
	  if (s > rl_end)
	    {
	      s1 = s = e1;
	      break;
	    }
	  /* Or past point if point is within an assignment statement */
	  else if (was_assignment && s > rl_point)
	    {
	      s1 = s = e1;
	      break;
	    }
	  /* Skip over assignment statements preceding a command name.  If we
	     don't find a command name at all, we can perform command name
	     completion.  If we find a partial command name, we should perform
	     command name completion on it. */
	  FREE (n);
	  n = find_cmd_name (s, &s1, &e1);
	  s = e1 + 1;
	}
      while (was_assignment = assignment (n, 0));
      s = s1;		/* reset to index where name begins */

      /* s == index of where command name begins (reset above)
	 e == end of current command, may be end of line
         s1 = index of where command name begins
         e1 == index of where command name ends
	 start == index of where word to be completed begins
	 end == index of where word to be completed ends
	 if (s == start) we are doing command word completion for sure
	 if (e1 == end) we are at the end of the command name and completing it */
      if (start == 0 && end == 0 && e != 0 && text[0] == '\0')	/* beginning of non-empty line */
        foundcs = 0;
      else if (start == end && start == s1 && e != 0 && e1 > end)	/* beginning of command name, leading whitespace */
	foundcs = 0;
      else if (e == 0 && e == s && text[0] == '\0' && have_progcomps)	/* beginning of empty line */
        prog_complete_matches = programmable_completions (EMPTYCMD, text, s, e, &foundcs);
      else if (start == end && text[0] == '\0' && s1 > start && whitespace (rl_line_buffer[start]))
        foundcs = 0;		/* whitespace before command name */
      else if (e > s && was_assignment == 0 && e1 == end && rl_line_buffer[e] == 0 && whitespace (rl_line_buffer[e-1]) == 0)
	{
	  /* not assignment statement, but still want to perform command
	     completion if we are composing command word. */
	  foundcs = 0;
	  in_command_position = s == start && STREQ (n, text);	/* XXX */
	}
      else if (e > s && was_assignment == 0 && have_progcomps)
	{
	  prog_complete_matches = programmable_completions (n, text, s, e, &foundcs);
	  /* command completion if programmable completion fails */
	  /* If we have a completion for the initial word, we can prefer that */
	  in_command_position = s == start && (iw_compspec || STREQ (n, text));	/* XXX */
	  if (iw_compspec && in_command_position)
	    foundcs = 0;
	}
      /* empty command name following command separator */
      else if (s >= e && n[0] == '\0' && text[0] == '\0' && start > 0 &&
		was_assignment == 0 && member (rl_line_buffer[start-1], COMMAND_SEPARATORS))
	{
	  foundcs = 0;
	  in_command_position = 1;
	}
      else if (s >= e && n[0] == '\0' && text[0] == '\0' && start > 0)
        {
          foundcs = 0;	/* empty command name following optional assignments */
          in_command_position += was_assignment;
        }
      else if (s == start && e == end && STREQ (n, text) && start > 0)
        {
          foundcs = 0;	/* partial command name following assignments */
          in_command_position = 1;
        }
      else
	foundcs = 0;

      /* If we have defined a compspec for the initial (command) word, call
	 it and process the results like any other programmable completion. */
      if (in_command_position && have_progcomps && foundcs == 0 && iw_compspec)
	prog_complete_matches = programmable_completions (INITIALWORD, text, s, e, &foundcs);

      FREE (n);
      /* XXX - if we found a COMPSPEC for the command, just return whatever
	 the programmable completion code returns, and disable the default
	 filename completion that readline will do unless the COPT_DEFAULT
	 option has been set with the `-o default' option to complete or
	 compopt. */
      if (foundcs)
	{
	  pcomp_set_readline_variables (foundcs, 1);
	  /* Turn what the programmable completion code returns into what
	     readline wants.  I should have made compute_lcd_of_matches
	     external... */
	  matches = rl_completion_matches (text, prog_complete_return);
	  if ((foundcs & COPT_DEFAULT) == 0)
	    rl_attempted_completion_over = 1;	/* no default */
	  if (matches || ((foundcs & COPT_BASHDEFAULT) == 0))
	    return (matches);
	}
    }
#endif

  if (matches == 0)
    {
      dflags = 0;
      if (in_command_position)
	dflags |= DEFCOMP_CMDPOS;
      matches = bash_default_completion (text, start, end, qc, dflags);
    }

  return matches;
}


bash_filename_stat_hook (dirname)
     char **dirname;
{
  char *local_dirname, *new_dirname, *t;
  int should_expand_dirname, return_value;
  int global_nounset;
  WORD_LIST *wl;

  local_dirname = *dirname;
  should_expand_dirname = return_value = 0;
  if (t = mbschr (local_dirname, '$'))
    should_expand_dirname = '$';
  else if (t = mbschr (local_dirname, '`'))	/* XXX */
    should_expand_dirname = '`';

  if (should_expand_dirname && directory_exists (local_dirname, 0))
    should_expand_dirname = 0;

  if (should_expand_dirname)
    {
      new_dirname = savestring (local_dirname);
      /* no error messages, and expand_prompt_string doesn't longjmp so we don't
	 have to worry about restoring this setting. */
      global_nounset = unbound_vars_is_error;
      unbound_vars_is_error = 0;
      wl = expand_prompt_string (new_dirname, 0, W_NOCOMSUB|W_NOPROCSUB|W_COMPLETE);	/* does the right thing */
      unbound_vars_is_error = global_nounset;
      if (wl)
	{
	  free (new_dirname);
	  new_dirname = string_list (wl);
	  /* Tell the completer we actually expanded something and change
	     *dirname only if we expanded to something non-null -- stat
	     behaves unpredictably when passed null or empty strings */
	  if (new_dirname && *new_dirname)
	    {
	      free (local_dirname);	/* XXX */
	      local_dirname = *dirname = new_dirname;
	      return_value = STREQ (local_dirname, *dirname) == 0;
	    }
	  else
	    free (new_dirname);
	  dispose_words (wl);
	}
      else
	free (new_dirname);
    }

  /* This is very similar to the code in bash_directory_completion_hook below,
     but without spelling correction and not worrying about whether or not
     we change relative pathnames. */
  if (no_symbolic_links == 0 && (local_dirname[0] != '.' || local_dirname[1]))
    {
      char *temp1, *temp2;

      t = get_working_directory ("symlink-hook");
      temp1 = make_absolute (local_dirname, t);
      free (t);
      temp2 = sh_canonpath (temp1, PATH_CHECKDOTDOT|PATH_CHECKEXISTS);

      /* If we can't canonicalize, bail. */
      if (temp2 == 0)
	{
	  free (temp1);
	  return return_value;
	}

      free (local_dirname);
      *dirname = temp2;
      free (temp1);
    }

  return (return_value);
}


maybe_make_restricted (name)
     char *name;
{
  char *temp;

  temp = base_pathname (name);
  if (*temp == '-')
    temp++;
  if (restricted || (STREQ (temp, RESTRICTED_SHELL_NAME)))
    {
#if defined (RBASH_STATIC_PATH_VALUE)
      bind_variable ("PATH", RBASH_STATIC_PATH_VALUE, 0);
      stupidly_hack_special_variables ("PATH");		/* clear hash table */
#endif
      set_var_read_only ("PATH");
      set_var_read_only ("SHELL");
      set_var_read_only ("ENV");
      set_var_read_only ("BASH_ENV");
      restricted = 1;
    }
  return (restricted);
}


history_completion_generator (hint_text, state)
     const char *hint_text;
     int state;
{
  static int local_index, len;
  static const char *text;

  /* If this is the first call to the generator, then initialize the
     list of strings to complete over. */
  if (state == 0)
    {
      if (dabbrev_expand_active)	/* This is kind of messy */
	rl_completion_suppress_append = 1;
      local_index = 0;
      build_history_completion_array ();
      text = hint_text;
      len = strlen (text);
    }

  while (history_completion_array && history_completion_array[local_index])
    {
      /* XXX - should this use completion-ignore-case? */
      if (strncmp (text, history_completion_array[local_index++], len) == 0)
	return (savestring (history_completion_array[local_index - 1]));
    }
  return ((char *)NULL);
}


maybe_make_readline_line (new_line)
     char *new_line;
{
  if (new_line && strcmp (new_line, rl_line_buffer) != 0)
    {
      rl_point = rl_end;

      rl_add_undo (UNDO_BEGIN, 0, 0, 0);
      rl_delete_text (0, rl_point);
      rl_point = rl_end = rl_mark = 0;
      rl_insert_text (new_line);
      rl_add_undo (UNDO_END, 0, 0, 0);
    }
}


quote_globbing_chars (string)
     const char *string;
{
  size_t slen;
  char *temp, *t;
  const char *s, *send;
  DECLARE_MBSTATE;

  slen = strlen (string);
  send = string + slen;

  temp = (char *)xmalloc (slen * 2 + 1);
  for (t = temp, s = string; *s; )
    {
      if (glob_char_p (s))
	*t++ = '\\';

      /* Copy a single (possibly multibyte) character from s to t,
	 incrementing both. */
      COPY_CHAR_P (t, s, send);
    }
  *t = '\0';
  return temp;
}


wskipname (pat, dname, flags)
     wchar_t *pat, *dname;
     int flags;
{
  if (glob_always_skip_dot_and_dotdot && WDOT_OR_DOTDOT (dname))
    return 1;

  /* If a leading dot need not be explicitly matched, and the
     pattern doesn't start with a `.', don't match `.' or `..' */
  if (noglob_dot_filenames == 0 && pat[0] != L'.' &&
	(pat[0] != L'\\' || pat[1] != L'.') &&
	WDOT_OR_DOTDOT (dname))
    return 1;

  /* If a leading dot must be explicitly matched, check to see if the
     pattern and dirname both have one. */
 else if (noglob_dot_filenames && dname[0] == L'.' &&
	pat[0] != L'.' &&
	   (pat[0] != L'\\' || pat[1] != L'.'))
    return 1;

  return 0;
}


unset_bash_input (check_zero)
     int check_zero;
{
#if defined (BUFFERED_INPUT)
  if ((check_zero && default_buffered_input >= 0) ||
      (check_zero == 0 && default_buffered_input > 0))
    {
      close_buffered_fd (default_buffered_input);
      default_buffered_input = bash_input.location.buffered_fd = -1;
      bash_input.type = st_none;		/* XXX */
    }
#else /* !BUFFERED_INPUT */
  if (default_input)
    {
      fclose (default_input);
      default_input = (FILE *)NULL;
    }
#endif /* !BUFFERED_INPUT */
}


bash_progcomp_ignore_filenames (names)
     char **names;
{
  _ignore_completion_names (names, test_for_canon_directory);
  return 0;
}


shell_glob_filename (pathname)
     const char *pathname;
{
#if defined (USE_POSIX_GLOB_LIBRARY)
  register int i;
  char *temp, **results;
  glob_t filenames;
  int glob_flags;

  temp = quote_string_for_globbing (pathname, QGLOB_FILENAME);

  filenames.gl_offs = 0;

#  if defined (GLOB_PERIOD)
  glob_flags = glob_dot_filenames ? GLOB_PERIOD : 0;
#  else
  glob_flags = 0;
#  endif /* !GLOB_PERIOD */

  glob_flags |= (GLOB_ERR | GLOB_DOOFFS);

  i = glob (temp, glob_flags, (posix_glob_errfunc_t *)NULL, &filenames);

  free (temp);

  if (i == GLOB_NOSPACE || i == GLOB_ABORTED)
    return ((char **)NULL);
  else if (i == GLOB_NOMATCH)
    filenames.gl_pathv = (char **)NULL;
  else if (i != 0)		/* other error codes not in POSIX.2 */
    filenames.gl_pathv = (char **)NULL;

  results = filenames.gl_pathv;

  if (results && ((GLOB_FAILED (results)) == 0))
    {
      if (should_ignore_glob_matches ())
	ignore_glob_matches (results);
      if (results && results[0])
	strvec_sort (results);
      else
	{
	  FREE (results);
	  results = (char **)NULL;
	}
    }

  return (results);

#else /* !USE_POSIX_GLOB_LIBRARY */

  char *temp, **results;
  int gflags, quoted_pattern;

  noglob_dot_filenames = glob_dot_filenames == 0;

  temp = quote_string_for_globbing (pathname, QGLOB_FILENAME);
  quoted_pattern = STREQ (pathname, temp) == 0;
  gflags = glob_star ? GX_GLOBSTAR : 0;
  results = glob_filename (temp, gflags);
  free (temp);

  if (results && ((GLOB_FAILED (results)) == 0))
    {
      if (should_ignore_glob_matches ())
	ignore_glob_matches (results);
      if (results && results[0])
	strvec_sort (results);
      else
	{
	  FREE (results);
	  results = (char **)&glob_error_return;
	}
    }

  return (results);
#endif /* !USE_POSIX_GLOB_LIBRARY */
}


extglob_skipname (pat, dname, flags)
     char *pat, *dname;
     int flags;
{
  char *pp, *pe, *t, *se;
  int n, r, negate, wild, nullpat;

  negate = *pat == '!';
  wild = *pat == '*' || *pat == '?';
  pp = pat + 2;
  se = pp + strlen (pp) - 1;		/* end of string */
  pe = glob_patscan (pp, se, 0);	/* end of extglob pattern (( */
  /* we should check for invalid extglob pattern here */
  if (pe == 0)
    return 0;

  /* if pe != se we have more of the pattern at the end of the extglob
     pattern. Check the easy case first ( */
  if (pe == se && *pe == ')' && (t = strchr (pp, '|')) == 0)
    {
      *pe = '\0';
#if defined (HANDLE_MULTIBYTE)
      r = mbskipname (pp, dname, flags);
#else
      r = skipname (pp, dname, flags);	/*(*/
#endif
      *pe = ')';
      if (wild && pe[1])	/* if we can match zero instances, check further */
        return (skipname (pe+1, dname, flags));
      return r;
    }

  /* Is the extglob pattern between the parens the null pattern?  The null
     pattern can match nothing, so should we check any remaining portion of
     the pattern? */
  nullpat = pe >= (pat + 2) && pe[-2] == '(' && pe[-1] == ')';

  /* check every subpattern */
  while (t = glob_patscan (pp, pe, '|'))
    {
      n = t[-1];	/* ( */
      if (extglob_pattern_p (pp) && n == ')')
	t[-1] = n;	/* no-op for now */
      else
	t[-1] = '\0';
#if defined (HANDLE_MULTIBYTE)
      r = mbskipname (pp, dname, flags);
#else
      r = skipname (pp, dname, flags);
#endif
      t[-1] = n;
      if (r == 0)	/* if any pattern says not skip, we don't skip */
        return r;
      pp = t;
    }	/*(*/

  /* glob_patscan might find end of string */
  if (pp == se)
    return r;

  /* but if it doesn't then we didn't match a leading dot */
  if (wild && *pe)	/* if we can match zero instances, check further */
    return (skipname (pe, dname, flags));
  return 1;
}