static int pfkey_register(struct sock *sk, struct sk_buff *skb, const struct sadb_msg *hdr, void * const *ext_hdrs)
{
	struct pfkey_sock *pfk = pfkey_sk(sk);
	struct sk_buff *supp_skb;

	if (hdr->sadb_msg_satype > SADB_SATYPE_MAX)
		return -EINVAL;

	if (hdr->sadb_msg_satype != SADB_SATYPE_UNSPEC) {
		if (pfk->registered&(1<<hdr->sadb_msg_satype))
			return -EEXIST;
		pfk->registered |= (1<<hdr->sadb_msg_satype);
	}

	mutex_lock(&pfkey_mutex);
	xfrm_probe_algs();

	supp_skb = compose_sadb_supported(hdr, GFP_KERNEL | __GFP_ZERO);
	mutex_unlock(&pfkey_mutex);

	if (!supp_skb) {
		if (hdr->sadb_msg_satype != SADB_SATYPE_UNSPEC)
			pfk->registered &= ~(1<<hdr->sadb_msg_satype);

		return -ENOBUFS;
	}

	pfkey_broadcast(supp_skb, GFP_KERNEL, BROADCAST_REGISTERED, sk,
			sock_net(sk));
	return 0;
}


int kvmppc_rtas_hcall(struct kvm_vcpu *vcpu)
{
	struct rtas_token_definition *d;
	struct rtas_args args;
	rtas_arg_t *orig_rets;
	gpa_t args_phys;
	int rc;

	/*
	 * r4 contains the guest physical address of the RTAS args
	 * Mask off the top 4 bits since this is a guest real address
	 */
	args_phys = kvmppc_get_gpr(vcpu, 4) & KVM_PAM;

	vcpu->srcu_idx = srcu_read_lock(&vcpu->kvm->srcu);
	rc = kvm_read_guest(vcpu->kvm, args_phys, &args, sizeof(args));
	srcu_read_unlock(&vcpu->kvm->srcu, vcpu->srcu_idx);
	if (rc)
		goto fail;

	/*
	 * args->rets is a pointer into args->args. Now that we've
	 * copied args we need to fix it up to point into our copy,
	 * not the guest args. We also need to save the original
	 * value so we can restore it on the way out.
	 */
	orig_rets = args.rets;
	args.rets = &args.args[be32_to_cpu(args.nargs)];

	mutex_lock(&vcpu->kvm->arch.rtas_token_lock);

	rc = -ENOENT;
	list_for_each_entry(d, &vcpu->kvm->arch.rtas_tokens, list) {
		if (d->token == be32_to_cpu(args.token)) {
			d->handler->handler(vcpu, &args);
			rc = 0;
			break;
		}
	}

	mutex_unlock(&vcpu->kvm->arch.rtas_token_lock);

	if (rc == 0) {
		args.rets = orig_rets;
		rc = kvm_write_guest(vcpu->kvm, args_phys, &args, sizeof(args));
		if (rc)
			goto fail;
	}

	return rc;

fail:
	/*
	 * We only get here if the guest has called RTAS with a bogus
	 * args pointer. That means we can't get to the args, and so we
	 * can't fail the RTAS call. So fail right out to userspace,
	 * which should kill the guest.
	 */
	return rc;
}

KVM: PPC: Book3S: Fix H_RTAS rets buffer overflow

The kvmppc_rtas_hcall() sets the host rtas_args.rets pointer based on
the rtas_args.nargs that was provided by the guest. That guest nargs
value is not range checked, so the guest can cause the host rets pointer
to be pointed outside the args array. The individual rtas function
handlers check the nargs and nrets values to ensure they are correct,
but if they are not, the handlers store a -3 (0xfffffffd) failure
indication in rets[0] which corrupts host memory.

Fix this by testing up front whether the guest supplied nargs and nret
would exceed the array size, and fail the hcall directly without storing
a failure indication to rets[0].

Also expand on a comment about why we kill the guest and try not to
return errors directly if we have a valid rets[0] pointer.

Fixes: 8e591cb72047 ("KVM: PPC: Book3S: Add infrastructure to implement kernel-side RTAS calls")
Cc: stable@vger.kernel.org # v3.10+
Reported-by: Alexey Kardashevskiy <aik@ozlabs.ru>
Signed-off-by: Nicholas Piggin <npiggin@gmail.com>
Signed-off-by: Michael Ellerman <mpe@ellerman.id.au>


int kvmppc_rtas_hcall(struct kvm_vcpu *vcpu)
{
	struct rtas_token_definition *d;
	struct rtas_args args;
	rtas_arg_t *orig_rets;
	gpa_t args_phys;
	int rc;

	/*
	 * r4 contains the guest physical address of the RTAS args
	 * Mask off the top 4 bits since this is a guest real address
	 */
	args_phys = kvmppc_get_gpr(vcpu, 4) & KVM_PAM;

	vcpu->srcu_idx = srcu_read_lock(&vcpu->kvm->srcu);
	rc = kvm_read_guest(vcpu->kvm, args_phys, &args, sizeof(args));
	srcu_read_unlock(&vcpu->kvm->srcu, vcpu->srcu_idx);
	if (rc)
		goto fail;

	/*
	 * args->rets is a pointer into args->args. Now that we've
	 * copied args we need to fix it up to point into our copy,
	 * not the guest args. We also need to save the original
	 * value so we can restore it on the way out.
	 */
	orig_rets = args.rets;
	if (be32_to_cpu(args.nargs) >= ARRAY_SIZE(args.args)) {
		/*
		 * Don't overflow our args array: ensure there is room for
		 * at least rets[0] (even if the call specifies 0 nret).
		 *
		 * Each handler must then check for the correct nargs and nret
		 * values, but they may always return failure in rets[0].
		 */
		rc = -EINVAL;
		goto fail;
	}
	args.rets = &args.args[be32_to_cpu(args.nargs)];

	mutex_lock(&vcpu->kvm->arch.rtas_token_lock);

	rc = -ENOENT;
	list_for_each_entry(d, &vcpu->kvm->arch.rtas_tokens, list) {
		if (d->token == be32_to_cpu(args.token)) {
			d->handler->handler(vcpu, &args);
			rc = 0;
			break;
		}
	}

	mutex_unlock(&vcpu->kvm->arch.rtas_token_lock);

	if (rc == 0) {
		args.rets = orig_rets;
		rc = kvm_write_guest(vcpu->kvm, args_phys, &args, sizeof(args));
		if (rc)
			goto fail;
	}

	return rc;

fail:
	/*
	 * We only get here if the guest has called RTAS with a bogus
	 * args pointer or nargs/nret values that would overflow the
	 * array. That means we can't get to the args, and so we can't
	 * fail the RTAS call. So fail right out to userspace, which
	 * should kill the guest.
	 *
	 * SLOF should actually pass the hcall return value from the
	 * rtas handler call in r3, so enter_rtas could be modified to
	 * return a failure indication in r3 and we could return such
	 * errors to the guest rather than failing to host userspace.
	 * However old guests that don't test for failure could then
	 * continue silently after errors, so for now we won't do this.
	 */
	return rc;
}


int qtm_decompress(struct qtm_stream *qtm, off_t out_bytes) {
  unsigned int frame_start, frame_end, window_posn, match_offset, range;
  unsigned char *window, *i_ptr, *i_end, *runsrc, *rundest;
  int i, j, selector, extra, sym, match_length, ret;
  unsigned short H, L, C, symf;

  register unsigned int bit_buffer;
  register unsigned char bits_left;
  unsigned char bits_needed, bit_run;

  /* easy answers */
  if (!qtm || (out_bytes < 0)) return CL_ENULLARG;
  if (qtm->error) return qtm->error;

  /* flush out any stored-up bytes before we begin */
  i = qtm->o_end - qtm->o_ptr;
  if ((off_t) i > out_bytes) i = (int) out_bytes;
  if (i) {
    if (qtm->wflag && (ret = mspack_write(qtm->ofd, qtm->o_ptr, i, qtm->file)) != CL_SUCCESS) {
      return qtm->error = ret;
    }
    qtm->o_ptr  += i;
    out_bytes   -= i;
  }
  if (out_bytes == 0) return CL_SUCCESS;

  /* restore local state */
  QTM_RESTORE_BITS;
  window = qtm->window;
  window_posn = qtm->window_posn;
  frame_start = qtm->frame_start;
  H = qtm->H;
  L = qtm->L;
  C = qtm->C;

  /* while we do not have enough decoded bytes in reserve: */
  while ((qtm->o_end - qtm->o_ptr) < out_bytes) {

    /* read header if necessary. Initialises H, L and C */
    if (!qtm->header_read) {
      H = 0xFFFF; L = 0; QTM_READ_BITS(C, 16);
      qtm->header_read = 1;
    }

    /* decode more, at most up to to frame boundary */
    frame_end = window_posn + (out_bytes - (qtm->o_end - qtm->o_ptr));
    if ((frame_start + QTM_FRAME_SIZE) < frame_end) {
      frame_end = frame_start + QTM_FRAME_SIZE;
    }

    while (window_posn < frame_end) {
      QTM_GET_SYMBOL(qtm->model7, selector);
      if (selector < 4) {
	struct qtm_model *mdl = (selector == 0) ? &qtm->model0 :
	                        ((selector == 1) ? &qtm->model1 :
				((selector == 2) ? &qtm->model2 :
                                                   &qtm->model3));
	QTM_GET_SYMBOL((*mdl), sym);
	window[window_posn++] = sym;
      }
      else {
	switch (selector) {
	case 4: /* selector 4 = fixed length match (3 bytes) */
	  QTM_GET_SYMBOL(qtm->model4, sym);
	  QTM_READ_BITS(extra, qtm->extra_bits[sym]);
	  match_offset = qtm->position_base[sym] + extra + 1;
	  match_length = 3;
	  break;

	case 5: /* selector 5 = fixed length match (4 bytes) */
	  QTM_GET_SYMBOL(qtm->model5, sym);
	  QTM_READ_BITS(extra, qtm->extra_bits[sym]);
	  match_offset = qtm->position_base[sym] + extra + 1;
	  match_length = 4;
	  break;

	case 6: /* selector 6 = variable length match */
	  QTM_GET_SYMBOL(qtm->model6len, sym);
	  QTM_READ_BITS(extra, qtm->length_extra[sym]);
	  match_length = qtm->length_base[sym] + extra + 5;

	  QTM_GET_SYMBOL(qtm->model6, sym);
	  QTM_READ_BITS(extra, qtm->extra_bits[sym]);
	  match_offset = qtm->position_base[sym] + extra + 1;
	  break;

	default:
	  /* should be impossible, model7 can only return 0-6 */
	  return qtm->error = CL_EFORMAT;
	}

	rundest = &window[window_posn];
	i = match_length;
	/* does match offset wrap the window? */
	if (match_offset > window_posn) {
	  /* j = length from match offset to end of window */
	  j = match_offset - window_posn;
	  if (j > (int) qtm->window_size) {
	    cli_dbgmsg("qtm_decompress: match offset beyond window boundaries\n");
	    return qtm->error = CL_EFORMAT;
	  }
	  runsrc = &window[qtm->window_size - j];
	  if (j < i) {
	    /* if match goes over the window edge, do two copy runs */
	    i -= j; while (j-- > 0) *rundest++ = *runsrc++;
	    runsrc = window;
	  }
	  while (i-- > 0) *rundest++ = *runsrc++;
	}
	else {
	  runsrc = rundest - match_offset;
	  if(i > (int) (qtm->window_size - window_posn))
	    i = qtm->window_size - window_posn;
	  while (i-- > 0) *rundest++ = *runsrc++;
	}
	window_posn += match_length;
      }
    } /* while (window_posn < frame_end) */

    qtm->o_end = &window[window_posn];

    /* another frame completed? */
    if ((window_posn - frame_start) >= QTM_FRAME_SIZE) {
      if ((window_posn - frame_start) != QTM_FRAME_SIZE) {
	cli_dbgmsg("qtm_decompress: overshot frame alignment\n");
	return qtm->error = CL_EFORMAT;
      }

      /* re-align input */
      if (bits_left & 7) QTM_REMOVE_BITS(bits_left & 7);
      do { QTM_READ_BITS(i, 8); } while (i != 0xFF);
      qtm->header_read = 0;

      /* window wrap? */
      if (window_posn == qtm->window_size) {
	/* flush all currently stored data */
	i = (qtm->o_end - qtm->o_ptr);
	if (qtm->wflag && (ret = mspack_write(qtm->ofd, qtm->o_ptr, i, qtm->file)) != CL_SUCCESS) {
	  return qtm->error = ret;
	}
	out_bytes -= i;
	qtm->o_ptr = &window[0];
	qtm->o_end = &window[0];
	window_posn = 0;
      }

      frame_start = window_posn;
    }

  } /* while (more bytes needed) */

  if (out_bytes) {
    i = (int) out_bytes;
    if (qtm->wflag && (ret = mspack_write(qtm->ofd, qtm->o_ptr, i, qtm->file)) != CL_SUCCESS) {
      return qtm->error = ret;
    }
    qtm->o_ptr += i;
  }

  /* store local state */
  QTM_STORE_BITS;
  qtm->window_posn = window_posn;
  qtm->frame_start = frame_start;
  qtm->H = H;
  qtm->L = L;
  qtm->C = C;

  return CL_SUCCESS;
}

libclamav/mspack.c: fix Quantum decompressor (bb#1771)


int qtm_decompress(struct qtm_stream *qtm, off_t out_bytes) {
  unsigned int frame_start, frame_end, window_posn, match_offset, range;
  unsigned char *window, *i_ptr, *i_end, *runsrc, *rundest;
  int i, j, selector, extra, sym, match_length, ret;
  unsigned short H, L, C, symf;

  register unsigned int bit_buffer;
  register unsigned char bits_left;
  unsigned char bits_needed, bit_run;

  /* easy answers */
  if (!qtm || (out_bytes < 0)) return CL_ENULLARG;
  if (qtm->error) return qtm->error;

  /* flush out any stored-up bytes before we begin */
  i = qtm->o_end - qtm->o_ptr;
  if ((off_t) i > out_bytes) i = (int) out_bytes;
  if (i) {
    if (qtm->wflag && (ret = mspack_write(qtm->ofd, qtm->o_ptr, i, qtm->file)) != CL_SUCCESS) {
      return qtm->error = ret;
    }
    qtm->o_ptr  += i;
    out_bytes   -= i;
  }
  if (out_bytes == 0) return CL_SUCCESS;

  /* restore local state */
  QTM_RESTORE_BITS;
  window = qtm->window;
  window_posn = qtm->window_posn;
  frame_start = qtm->frame_start;
  H = qtm->H;
  L = qtm->L;
  C = qtm->C;

  /* while we do not have enough decoded bytes in reserve: */
  while ((qtm->o_end - qtm->o_ptr) < out_bytes) {

    /* read header if necessary. Initialises H, L and C */
    if (!qtm->header_read) {
      H = 0xFFFF; L = 0; QTM_READ_BITS(C, 16);
      qtm->header_read = 1;
    }

    /* decode more, at most up to to frame boundary */
    frame_end = window_posn + (out_bytes - (qtm->o_end - qtm->o_ptr));
    if ((frame_start + QTM_FRAME_SIZE) < frame_end) {
      frame_end = frame_start + QTM_FRAME_SIZE;
    }

    while (window_posn < frame_end) {
      QTM_GET_SYMBOL(qtm->model7, selector);
      if (selector < 4) {
	struct qtm_model *mdl = (selector == 0) ? &qtm->model0 :
	                        ((selector == 1) ? &qtm->model1 :
				((selector == 2) ? &qtm->model2 :
                                                   &qtm->model3));
	QTM_GET_SYMBOL((*mdl), sym);
	window[window_posn++] = sym;
      }
      else {
	switch (selector) {
	case 4: /* selector 4 = fixed length match (3 bytes) */
	  QTM_GET_SYMBOL(qtm->model4, sym);
	  QTM_READ_BITS(extra, qtm->extra_bits[sym]);
	  match_offset = qtm->position_base[sym] + extra + 1;
	  match_length = 3;
	  break;

	case 5: /* selector 5 = fixed length match (4 bytes) */
	  QTM_GET_SYMBOL(qtm->model5, sym);
	  QTM_READ_BITS(extra, qtm->extra_bits[sym]);
	  match_offset = qtm->position_base[sym] + extra + 1;
	  match_length = 4;
	  break;

	case 6: /* selector 6 = variable length match */
	  QTM_GET_SYMBOL(qtm->model6len, sym);
	  QTM_READ_BITS(extra, qtm->length_extra[sym]);
	  match_length = qtm->length_base[sym] + extra + 5;

	  QTM_GET_SYMBOL(qtm->model6, sym);
	  QTM_READ_BITS(extra, qtm->extra_bits[sym]);
	  match_offset = qtm->position_base[sym] + extra + 1;
	  break;

	default:
	  /* should be impossible, model7 can only return 0-6 */
	  return qtm->error = CL_EFORMAT;
	}

	if (window_posn + match_length > qtm->window_size) {
	  cli_dbgmsg("qtm_decompress: match ran over window wrap\n");
	  return qtm->error = CL_EFORMAT;
	}

	rundest = &window[window_posn];
	i = match_length;
	/* does match offset wrap the window? */
	if (match_offset > window_posn) {
	  /* j = length from match offset to end of window */
	  j = match_offset - window_posn;
	  if (j > (int) qtm->window_size) {
	    cli_dbgmsg("qtm_decompress: match offset beyond window boundaries\n");
	    return qtm->error = CL_EFORMAT;
	  }
	  runsrc = &window[qtm->window_size - j];
	  if (j < i) {
	    /* if match goes over the window edge, do two copy runs */
	    i -= j; while (j-- > 0) *rundest++ = *runsrc++;
	    runsrc = window;
	  }
	  while (i-- > 0) *rundest++ = *runsrc++;
	}
	else {
	  runsrc = rundest - match_offset;
	  if(i > (int) (qtm->window_size - window_posn))
	    i = qtm->window_size - window_posn;
	  while (i-- > 0) *rundest++ = *runsrc++;
	}
	window_posn += match_length;
      }
    } /* while (window_posn < frame_end) */

    qtm->o_end = &window[window_posn];

    /* another frame completed? */
    if ((window_posn - frame_start) >= QTM_FRAME_SIZE) {
      if ((window_posn - frame_start) != QTM_FRAME_SIZE) {
	cli_dbgmsg("qtm_decompress: overshot frame alignment\n");
	return qtm->error = CL_EFORMAT;
      }

      /* re-align input */
      if (bits_left & 7) QTM_REMOVE_BITS(bits_left & 7);
      do { QTM_READ_BITS(i, 8); } while (i != 0xFF);
      qtm->header_read = 0;

      /* window wrap? */
      if (window_posn == qtm->window_size) {
	/* flush all currently stored data */
	i = (qtm->o_end - qtm->o_ptr);
	if (qtm->wflag && (ret = mspack_write(qtm->ofd, qtm->o_ptr, i, qtm->file)) != CL_SUCCESS) {
	  return qtm->error = ret;
	}
	out_bytes -= i;
	qtm->o_ptr = &window[0];
	qtm->o_end = &window[0];
	window_posn = 0;
      }

      frame_start = window_posn;
    }

  } /* while (more bytes needed) */

  if (out_bytes) {
    i = (int) out_bytes;
    if (qtm->wflag && (ret = mspack_write(qtm->ofd, qtm->o_ptr, i, qtm->file)) != CL_SUCCESS) {
      return qtm->error = ret;
    }
    qtm->o_ptr += i;
  }

  /* store local state */
  QTM_STORE_BITS;
  qtm->window_posn = window_posn;
  qtm->frame_start = frame_start;
  qtm->H = H;
  qtm->L = L;
  qtm->C = C;

  return CL_SUCCESS;
}


void simplestring_addn(simplestring* target, const char* source, int add_len) {
   if(target && source) {
      if(!target->str) {
         simplestring_init_str(target);
      }
      if(target->len + add_len + 1 > target->size) {
         /* newsize is current length + new length */
         int newsize = target->len + add_len + 1;
         int incr = target->size * 2;

         /* align to SIMPLESTRING_INCR increments */
         newsize = newsize - (newsize % incr) + incr;
         target->str = (char*)realloc(target->str, newsize);

         target->size = target->str ? newsize : 0;
      }

      if(target->str) {
         if(add_len) {
            memcpy(target->str + target->len, source, add_len);
         }
         target->len += add_len;
         target->str[target->len] = 0; /* null terminate */
      }
   }
}

Fix bug #72606: heap-buffer-overflow (write) simplestring_addn simplestring.c


void simplestring_addn(simplestring* target, const char* source, size_t add_len) {
   size_t newsize = target->size, incr = 0;
   if(target && source) {
      if(!target->str) {
         simplestring_init_str(target);
      }

      if((SIZE_MAX - add_len) < target->len || (SIZE_MAX - add_len - 1) < target->len) {
    	  /* check for overflows, if there's a potential overflow do nothing */
    	  return;
      }

      if(target->len + add_len + 1 > target->size) {
         /* newsize is current length + new length */
         newsize = target->len + add_len + 1;
         incr = target->size * 2;

         /* align to SIMPLESTRING_INCR increments */
         if (incr) {
            newsize = newsize - (newsize % incr) + incr;
         }
         if(newsize < (target->len + add_len + 1)) {
        	 /* some kind of overflow happened */
        	 return;
         }
         target->str = (char*)realloc(target->str, newsize);

         target->size = target->str ? newsize : 0;
      }

      if(target->str) {
         if(add_len) {
            memcpy(target->str + target->len, source, add_len);
         }
         target->len += add_len;
         target->str[target->len] = 0; /* null terminate */
      }
   }
}


static int mlx5_fpga_conn_create_cq(struct mlx5_fpga_conn *conn, int cq_size)
{
	struct mlx5_fpga_device *fdev = conn->fdev;
	struct mlx5_core_dev *mdev = fdev->mdev;
	u32 temp_cqc[MLX5_ST_SZ_DW(cqc)] = {0};
	u32 out[MLX5_ST_SZ_DW(create_cq_out)];
	struct mlx5_wq_param wqp;
	struct mlx5_cqe64 *cqe;
	int inlen, err, eqn;
	unsigned int irqn;
	void *cqc, *in;
	__be64 *pas;
	u32 i;

	cq_size = roundup_pow_of_two(cq_size);
	MLX5_SET(cqc, temp_cqc, log_cq_size, ilog2(cq_size));

	wqp.buf_numa_node = mdev->priv.numa_node;
	wqp.db_numa_node  = mdev->priv.numa_node;

	err = mlx5_cqwq_create(mdev, &wqp, temp_cqc, &conn->cq.wq,
			       &conn->cq.wq_ctrl);
	if (err)
		return err;

	for (i = 0; i < mlx5_cqwq_get_size(&conn->cq.wq); i++) {
		cqe = mlx5_cqwq_get_wqe(&conn->cq.wq, i);
		cqe->op_own = MLX5_CQE_INVALID << 4 | MLX5_CQE_OWNER_MASK;
	}

	inlen = MLX5_ST_SZ_BYTES(create_cq_in) +
		sizeof(u64) * conn->cq.wq_ctrl.buf.npages;
	in = kvzalloc(inlen, GFP_KERNEL);
	if (!in) {
		err = -ENOMEM;
		goto err_cqwq;
	}

	err = mlx5_vector2eqn(mdev, smp_processor_id(), &eqn, &irqn);
	if (err)
		goto err_cqwq;

	cqc = MLX5_ADDR_OF(create_cq_in, in, cq_context);
	MLX5_SET(cqc, cqc, log_cq_size, ilog2(cq_size));
	MLX5_SET(cqc, cqc, c_eqn, eqn);
	MLX5_SET(cqc, cqc, uar_page, fdev->conn_res.uar->index);
	MLX5_SET(cqc, cqc, log_page_size, conn->cq.wq_ctrl.buf.page_shift -
			   MLX5_ADAPTER_PAGE_SHIFT);
	MLX5_SET64(cqc, cqc, dbr_addr, conn->cq.wq_ctrl.db.dma);

	pas = (__be64 *)MLX5_ADDR_OF(create_cq_in, in, pas);
	mlx5_fill_page_frag_array(&conn->cq.wq_ctrl.buf, pas);

	err = mlx5_core_create_cq(mdev, &conn->cq.mcq, in, inlen, out, sizeof(out));
	kvfree(in);

	if (err)
		goto err_cqwq;

	conn->cq.mcq.cqe_sz     = 64;
	conn->cq.mcq.set_ci_db  = conn->cq.wq_ctrl.db.db;
	conn->cq.mcq.arm_db     = conn->cq.wq_ctrl.db.db + 1;
	*conn->cq.mcq.set_ci_db = 0;
	*conn->cq.mcq.arm_db    = 0;
	conn->cq.mcq.vector     = 0;
	conn->cq.mcq.comp       = mlx5_fpga_conn_cq_complete;
	conn->cq.mcq.event      = mlx5_fpga_conn_cq_event;
	conn->cq.mcq.irqn       = irqn;
	conn->cq.mcq.uar        = fdev->conn_res.uar;
	tasklet_init(&conn->cq.tasklet, mlx5_fpga_conn_cq_tasklet,
		     (unsigned long)conn);

	mlx5_fpga_dbg(fdev, "Created CQ #0x%x\n", conn->cq.mcq.cqn);

	goto out;

err_cqwq:
	mlx5_wq_destroy(&conn->cq.wq_ctrl);
out:
	return err;
}

net/mlx5: prevent memory leak in mlx5_fpga_conn_create_cq

In mlx5_fpga_conn_create_cq if mlx5_vector2eqn fails the allocated
memory should be released.

Fixes: 537a50574175 ("net/mlx5: FPGA, Add high-speed connection routines")
Signed-off-by: Navid Emamdoost <navid.emamdoost@gmail.com>
Signed-off-by: Saeed Mahameed <saeedm@mellanox.com>


static int mlx5_fpga_conn_create_cq(struct mlx5_fpga_conn *conn, int cq_size)
{
	struct mlx5_fpga_device *fdev = conn->fdev;
	struct mlx5_core_dev *mdev = fdev->mdev;
	u32 temp_cqc[MLX5_ST_SZ_DW(cqc)] = {0};
	u32 out[MLX5_ST_SZ_DW(create_cq_out)];
	struct mlx5_wq_param wqp;
	struct mlx5_cqe64 *cqe;
	int inlen, err, eqn;
	unsigned int irqn;
	void *cqc, *in;
	__be64 *pas;
	u32 i;

	cq_size = roundup_pow_of_two(cq_size);
	MLX5_SET(cqc, temp_cqc, log_cq_size, ilog2(cq_size));

	wqp.buf_numa_node = mdev->priv.numa_node;
	wqp.db_numa_node  = mdev->priv.numa_node;

	err = mlx5_cqwq_create(mdev, &wqp, temp_cqc, &conn->cq.wq,
			       &conn->cq.wq_ctrl);
	if (err)
		return err;

	for (i = 0; i < mlx5_cqwq_get_size(&conn->cq.wq); i++) {
		cqe = mlx5_cqwq_get_wqe(&conn->cq.wq, i);
		cqe->op_own = MLX5_CQE_INVALID << 4 | MLX5_CQE_OWNER_MASK;
	}

	inlen = MLX5_ST_SZ_BYTES(create_cq_in) +
		sizeof(u64) * conn->cq.wq_ctrl.buf.npages;
	in = kvzalloc(inlen, GFP_KERNEL);
	if (!in) {
		err = -ENOMEM;
		goto err_cqwq;
	}

	err = mlx5_vector2eqn(mdev, smp_processor_id(), &eqn, &irqn);
	if (err) {
		kvfree(in);
		goto err_cqwq;
	}

	cqc = MLX5_ADDR_OF(create_cq_in, in, cq_context);
	MLX5_SET(cqc, cqc, log_cq_size, ilog2(cq_size));
	MLX5_SET(cqc, cqc, c_eqn, eqn);
	MLX5_SET(cqc, cqc, uar_page, fdev->conn_res.uar->index);
	MLX5_SET(cqc, cqc, log_page_size, conn->cq.wq_ctrl.buf.page_shift -
			   MLX5_ADAPTER_PAGE_SHIFT);
	MLX5_SET64(cqc, cqc, dbr_addr, conn->cq.wq_ctrl.db.dma);

	pas = (__be64 *)MLX5_ADDR_OF(create_cq_in, in, pas);
	mlx5_fill_page_frag_array(&conn->cq.wq_ctrl.buf, pas);

	err = mlx5_core_create_cq(mdev, &conn->cq.mcq, in, inlen, out, sizeof(out));
	kvfree(in);

	if (err)
		goto err_cqwq;

	conn->cq.mcq.cqe_sz     = 64;
	conn->cq.mcq.set_ci_db  = conn->cq.wq_ctrl.db.db;
	conn->cq.mcq.arm_db     = conn->cq.wq_ctrl.db.db + 1;
	*conn->cq.mcq.set_ci_db = 0;
	*conn->cq.mcq.arm_db    = 0;
	conn->cq.mcq.vector     = 0;
	conn->cq.mcq.comp       = mlx5_fpga_conn_cq_complete;
	conn->cq.mcq.event      = mlx5_fpga_conn_cq_event;
	conn->cq.mcq.irqn       = irqn;
	conn->cq.mcq.uar        = fdev->conn_res.uar;
	tasklet_init(&conn->cq.tasklet, mlx5_fpga_conn_cq_tasklet,
		     (unsigned long)conn);

	mlx5_fpga_dbg(fdev, "Created CQ #0x%x\n", conn->cq.mcq.cqn);

	goto out;

err_cqwq:
	mlx5_wq_destroy(&conn->cq.wq_ctrl);
out:
	return err;
}


static int qh_help(int sd, char *buf, unsigned int len)
{
	struct query_handler *qh = NULL;

	if (!*buf || !strcmp(buf, "help")) {
		nsock_printf_nul(sd,
			"  help <name>   show help for handler <name>\n"
			"  help list     list registered handlers\n");
		return 0;
	}

	if (!strcmp(buf, "list")) {

		for (qh = qhandlers; qh != NULL; qh = qh->next_qh) {
			nsock_printf(sd, "%-10s %s\n", qh->name, qh->description ? qh->description : "(No description available)");
		}

		nsock_printf(sd, "%c", 0);
		return 0;
	}

	qh = qh_find_handler(buf);
	if (qh == NULL) {

		nsock_printf_nul(sd, "No handler named '%s' is registered\n", buf);

	} else if (qh->handler(sd, "help", 4) > 200) {

		nsock_printf_nul(sd, "The handler %s doesn't have any help yet.", buf);
	}

	return 0;
}

merge accidental master PR (#553) into maint and fix changelog
merge conflict


static int qh_help(int sd, char *buf, unsigned int len)
{
	struct query_handler *qh = NULL;

	if (buf == NULL || !strcmp(buf, "help")) {
		nsock_printf_nul(sd,
			"  help <name>   show help for handler <name>\n"
			"  help list     list registered handlers\n");
		return 0;
	}

	if (!strcmp(buf, "list")) {

		for (qh = qhandlers; qh != NULL; qh = qh->next_qh) {
			nsock_printf(sd, "%-10s %s\n", qh->name, qh->description ? qh->description : "(No description available)");
		}

		nsock_printf(sd, "%c", 0);
		return 0;
	}

	qh = qh_find_handler(buf);
	if (qh == NULL) {

		nsock_printf_nul(sd, "No handler named '%s' is registered\n", buf);

	} else if (qh->handler(sd, "help", 4) > 200) {

		nsock_printf_nul(sd, "The handler %s doesn't have any help yet.", buf);
	}

	return 0;
}


compileRule(FileInfo *file, TranslationTableHeader **table,
		DisplayTableHeader **displayTable, const MacroList **inScopeMacros) {
	CharsString token;
	TranslationTableOpcode opcode;
	CharsString ruleChars;
	CharsString ruleDots;
	CharsString cells;
	CharsString scratchPad;
	CharsString emphClass;
	TranslationTableCharacterAttributes after = 0;
	TranslationTableCharacterAttributes before = 0;
	int noback, nofor, nocross;
	noback = nofor = nocross = 0;
doOpcode:
	if (!getToken(file, &token, NULL)) return 1;				  /* blank line */
	if (token.chars[0] == '#' || token.chars[0] == '<') return 1; /* comment */
	if (file->lineNumber == 1 &&
			(eqasc2uni((unsigned char *)"ISO", token.chars, 3) ||
					eqasc2uni((unsigned char *)"UTF-8", token.chars, 5))) {
		if (table)
			compileHyphenation(file, &token, table);
		else
			/* ignore the whole file */
			while (_lou_getALine(file))
				;
		return 1;
	}
	opcode = getOpcode(file, &token);
	switch (opcode) {
	case CTO_Macro: {
		const Macro *macro;
#ifdef ENABLE_MACROS
		if (!inScopeMacros) {
			compileError(file, "Defining macros only allowed in table files.");
			return 0;
		}
		if (compileMacro(file, &macro)) {
			*inScopeMacros = cons_macro(macro, *inScopeMacros);
			return 1;
		}
		return 0;
#else
		compileError(file, "Macro feature is disabled.");
		return 0;
#endif
	}
	case CTO_IncludeFile: {
		CharsString includedFile;
		if (!getToken(file, &token, "include file name")) return 0;
		if (!parseChars(file, &includedFile, &token)) return 0;
		return includeFile(file, &includedFile, table, displayTable);
	}
	case CTO_NoBack:
		if (nofor) {
			compileError(file, "%s already specified.", _lou_findOpcodeName(CTO_NoFor));
			return 0;
		}
		noback = 1;
		goto doOpcode;
	case CTO_NoFor:
		if (noback) {
			compileError(file, "%s already specified.", _lou_findOpcodeName(CTO_NoBack));
			return 0;
		}
		nofor = 1;
		goto doOpcode;
	case CTO_Space:
		return compileCharDef(
				file, opcode, CTC_Space, noback, nofor, table, displayTable);
	case CTO_Digit:
		return compileCharDef(
				file, opcode, CTC_Digit, noback, nofor, table, displayTable);
	case CTO_LitDigit:
		return compileCharDef(
				file, opcode, CTC_LitDigit, noback, nofor, table, displayTable);
	case CTO_Punctuation:
		return compileCharDef(
				file, opcode, CTC_Punctuation, noback, nofor, table, displayTable);
	case CTO_Math:
		return compileCharDef(file, opcode, CTC_Math, noback, nofor, table, displayTable);
	case CTO_Sign:
		return compileCharDef(file, opcode, CTC_Sign, noback, nofor, table, displayTable);
	case CTO_Letter:
		return compileCharDef(
				file, opcode, CTC_Letter, noback, nofor, table, displayTable);
	case CTO_UpperCase:
		return compileCharDef(
				file, opcode, CTC_UpperCase, noback, nofor, table, displayTable);
	case CTO_LowerCase:
		return compileCharDef(
				file, opcode, CTC_LowerCase, noback, nofor, table, displayTable);
	case CTO_Grouping:
		return compileGrouping(file, noback, nofor, table, displayTable);
	case CTO_Display:
		if (!displayTable) return 1;  // ignore
		if (!getRuleCharsText(file, &ruleChars)) return 0;
		if (!getRuleDotsPattern(file, &ruleDots)) return 0;
		if (ruleChars.length != 1 || ruleDots.length != 1) {
			compileError(file, "Exactly one character and one cell are required.");
			return 0;
		}
		return putCharDotsMapping(
				file, ruleChars.chars[0], ruleDots.chars[0], displayTable);
	case CTO_UpLow:
	case CTO_None: {
		// check if token is a macro name
		if (inScopeMacros) {
			const MacroList *macros = *inScopeMacros;
			while (macros) {
				const Macro *m = macros->head;
				if (token.length == strlen(m->name) &&
						eqasc2uni((unsigned char *)m->name, token.chars, token.length)) {
					if (!inScopeMacros) {
						compileError(file, "Calling macros only allowed in table files.");
						return 0;
					}
					FileInfo tmpFile;
					memset(&tmpFile, 0, sizeof(tmpFile));
					tmpFile.fileName = file->fileName;
					tmpFile.sourceFile = file->sourceFile;
					tmpFile.lineNumber = file->lineNumber;
					tmpFile.encoding = noEncoding;
					tmpFile.status = 0;
					tmpFile.linepos = 0;
					tmpFile.linelen = 0;
					int argument_count = 0;
					CharsString *arguments =
							malloc(m->argument_count * sizeof(CharsString));
					while (argument_count < m->argument_count) {
						if (getToken(file, &token, "macro argument"))
							arguments[argument_count++] = token;
						else
							break;
					}
					if (argument_count < m->argument_count) {
						compileError(file, "Expected %d arguments", m->argument_count);
						return 0;
					}
					int i = 0;
					int subst = 0;
					int next = subst < m->substitution_count ? m->substitutions[2 * subst]
															 : m->definition_length;
					for (;;) {
						while (i < next) {
							widechar c = m->definition[i++];
							if (c == '\n') {
								if (!compileRule(&tmpFile, table, displayTable,
											inScopeMacros)) {
									_lou_logMessage(LOU_LOG_ERROR,
											"result of macro expansion was: %s",
											_lou_showString(
													tmpFile.line, tmpFile.linelen, 0));
									return 0;
								}
								tmpFile.linepos = 0;
								tmpFile.linelen = 0;
							} else if (tmpFile.linelen >= MAXSTRING) {
								compileError(file,
										"Line exceeds %d characters (post macro "
										"expansion)",
										MAXSTRING);
								return 0;
							} else
								tmpFile.line[tmpFile.linelen++] = c;
						}
						if (subst < m->substitution_count) {
							CharsString arg =
									arguments[m->substitutions[2 * subst + 1] - 1];
							for (int j = 0; j < arg.length; j++)
								tmpFile.line[tmpFile.linelen++] = arg.chars[j];
							subst++;
							next = subst < m->substitution_count
									? m->substitutions[2 * subst]
									: m->definition_length;
						} else {
							if (!compileRule(
										&tmpFile, table, displayTable, inScopeMacros)) {
								_lou_logMessage(LOU_LOG_ERROR,
										"result of macro expansion was: %s",
										_lou_showString(
												tmpFile.line, tmpFile.linelen, 0));
								return 0;
							}
							break;
						}
					}
					return 1;
				}
				macros = macros->tail;
			}
		}
		if (opcode == CTO_UpLow) {
			compileError(file, "The uplow opcode is deprecated.");
			return 0;
		}
		compileError(file, "opcode %s not defined.",
				_lou_showString(token.chars, token.length, 0));
		return 0;
	}

	/* now only opcodes follow that don't modify the display table */
	default:
		if (!table) return 1;
		switch (opcode) {
		case CTO_Locale:
			compileWarning(file,
					"The locale opcode is not implemented. Use the locale meta data "
					"instead.");
			return 1;
		case CTO_Undefined: {
			// not passing pointer because compileBrailleIndicator may reallocate table
			TranslationTableOffset ruleOffset = (*table)->undefined;
			if (!compileBrailleIndicator(file, "undefined character opcode",
						CTO_Undefined, &ruleOffset, noback, nofor, table))
				return 0;
			(*table)->undefined = ruleOffset;
			return 1;
		}
		case CTO_Match: {
			int ok = 0;
			widechar *patterns = NULL;
			TranslationTableRule *rule;
			TranslationTableOffset ruleOffset;
			CharsString ptn_before, ptn_after;
			TranslationTableOffset patternsOffset;
			int len, mrk;
			size_t patternsByteSize = sizeof(*patterns) * 27720;
			patterns = (widechar *)malloc(patternsByteSize);
			if (!patterns) _lou_outOfMemory();
			memset(patterns, 0xffff, patternsByteSize);
			noback = 1;
			getCharacters(file, &ptn_before);
			getRuleCharsText(file, &ruleChars);
			getCharacters(file, &ptn_after);
			getRuleDotsPattern(file, &ruleDots);
			if (!addRule(file, opcode, &ruleChars, &ruleDots, after, before, &ruleOffset,
						&rule, noback, nofor, table))
				goto CTO_Match_cleanup;
			if (ptn_before.chars[0] == '-' && ptn_before.length == 1)
				len = _lou_pattern_compile(
						&ptn_before.chars[0], 0, &patterns[1], 13841, *table, file);
			else
				len = _lou_pattern_compile(&ptn_before.chars[0], ptn_before.length,
						&patterns[1], 13841, *table, file);
			if (!len) goto CTO_Match_cleanup;
			mrk = patterns[0] = len + 1;
			_lou_pattern_reverse(&patterns[1]);
			if (ptn_after.chars[0] == '-' && ptn_after.length == 1)
				len = _lou_pattern_compile(
						&ptn_after.chars[0], 0, &patterns[mrk], 13841, *table, file);
			else
				len = _lou_pattern_compile(&ptn_after.chars[0], ptn_after.length,
						&patterns[mrk], 13841, *table, file);
			if (!len) goto CTO_Match_cleanup;
			len += mrk;
			if (!allocateSpaceInTranslationTable(
						file, &patternsOffset, len * sizeof(widechar), table))
				goto CTO_Match_cleanup;
			// allocateSpaceInTranslationTable may have moved table, so make sure rule is
			// still valid
			rule = (TranslationTableRule *)&(*table)->ruleArea[ruleOffset];
			memcpy(&(*table)->ruleArea[patternsOffset], patterns, len * sizeof(widechar));
			rule->patterns = patternsOffset;
			ok = 1;
		CTO_Match_cleanup:
			free(patterns);
			return ok;
		}

		case CTO_BackMatch: {
			int ok = 0;
			widechar *patterns = NULL;
			TranslationTableRule *rule;
			TranslationTableOffset ruleOffset;
			CharsString ptn_before, ptn_after;
			TranslationTableOffset patternOffset;
			int len, mrk;
			size_t patternsByteSize = sizeof(*patterns) * 27720;
			patterns = (widechar *)malloc(patternsByteSize);
			if (!patterns) _lou_outOfMemory();
			memset(patterns, 0xffff, patternsByteSize);
			nofor = 1;
			getCharacters(file, &ptn_before);
			getRuleCharsText(file, &ruleChars);
			getCharacters(file, &ptn_after);
			getRuleDotsPattern(file, &ruleDots);
			if (!addRule(file, opcode, &ruleChars, &ruleDots, 0, 0, &ruleOffset, &rule,
						noback, nofor, table))
				goto CTO_BackMatch_cleanup;
			if (ptn_before.chars[0] == '-' && ptn_before.length == 1)
				len = _lou_pattern_compile(
						&ptn_before.chars[0], 0, &patterns[1], 13841, *table, file);
			else
				len = _lou_pattern_compile(&ptn_before.chars[0], ptn_before.length,
						&patterns[1], 13841, *table, file);
			if (!len) goto CTO_BackMatch_cleanup;
			mrk = patterns[0] = len + 1;
			_lou_pattern_reverse(&patterns[1]);
			if (ptn_after.chars[0] == '-' && ptn_after.length == 1)
				len = _lou_pattern_compile(
						&ptn_after.chars[0], 0, &patterns[mrk], 13841, *table, file);
			else
				len = _lou_pattern_compile(&ptn_after.chars[0], ptn_after.length,
						&patterns[mrk], 13841, *table, file);
			if (!len) goto CTO_BackMatch_cleanup;
			len += mrk;
			if (!allocateSpaceInTranslationTable(
						file, &patternOffset, len * sizeof(widechar), table))
				goto CTO_BackMatch_cleanup;
			// allocateSpaceInTranslationTable may have moved table, so make sure rule is
			// still valid
			rule = (TranslationTableRule *)&(*table)->ruleArea[ruleOffset];
			memcpy(&(*table)->ruleArea[patternOffset], patterns, len * sizeof(widechar));
			rule->patterns = patternOffset;
			ok = 1;
		CTO_BackMatch_cleanup:
			free(patterns);
			return ok;
		}

		case CTO_CapsLetter:
		case CTO_BegCapsWord:
		case CTO_EndCapsWord:
		case CTO_BegCaps:
		case CTO_EndCaps:
		case CTO_BegCapsPhrase:
		case CTO_EndCapsPhrase:
		case CTO_LenCapsPhrase:
		/* these 8 general purpose opcodes are compiled further down to more specific
		 * internal opcodes:
		 * - modeletter
		 * - begmodeword
		 * - endmodeword
		 * - begmode
		 * - endmode
		 * - begmodephrase
		 * - endmodephrase
		 * - lenmodephrase
		 */
		case CTO_ModeLetter:
		case CTO_BegModeWord:
		case CTO_EndModeWord:
		case CTO_BegMode:
		case CTO_EndMode:
		case CTO_BegModePhrase:
		case CTO_EndModePhrase:
		case CTO_LenModePhrase: {
			TranslationTableCharacterAttributes mode;
			int i;
			switch (opcode) {
			case CTO_CapsLetter:
			case CTO_BegCapsWord:
			case CTO_EndCapsWord:
			case CTO_BegCaps:
			case CTO_EndCaps:
			case CTO_BegCapsPhrase:
			case CTO_EndCapsPhrase:
			case CTO_LenCapsPhrase:
				mode = CTC_UpperCase;
				i = 0;
				opcode += (CTO_ModeLetter - CTO_CapsLetter);
				break;
			default:
				if (!getToken(file, &token, "attribute name")) return 0;
				if (!(*table)->characterClasses && !allocateCharacterClasses(*table)) {
					return 0;
				}
				const CharacterClass *characterClass = findCharacterClass(&token, *table);
				if (!characterClass) {
					characterClass =
							addCharacterClass(file, token.chars, token.length, *table, 1);
					if (!characterClass) return 0;
				}
				mode = characterClass->attribute;
				if (!(mode == CTC_UpperCase || mode == CTC_Digit) && mode >= CTC_Space &&
						mode <= CTC_LitDigit) {
					compileError(file,
							"mode must be \"uppercase\", \"digit\", or a custom "
							"attribute name.");
					return 0;
				}
				/* check if this mode is already defined and if the number of modes does
				 * not exceed the maximal number */
				if (mode == CTC_UpperCase)
					i = 0;
				else {
					for (i = 1; i < MAX_MODES && (*table)->modes[i].value; i++) {
						if ((*table)->modes[i].mode == mode) {
							break;
						}
					}
					if (i == MAX_MODES) {
						compileError(file, "Max number of modes (%i) reached", MAX_MODES);
						return 0;
					}
				}
			}
			if (!(*table)->modes[i].value)
				(*table)->modes[i] = (EmphasisClass){ plain_text, mode,
					0x1 << (MAX_EMPH_CLASSES + i), MAX_EMPH_CLASSES + i };
			switch (opcode) {
			case CTO_BegModePhrase: {
				// not passing pointer because compileBrailleIndicator may reallocate
				// table
				TranslationTableOffset ruleOffset =
						(*table)->emphRules[MAX_EMPH_CLASSES + i][begPhraseOffset];
				if (!compileBrailleIndicator(file, "first word capital sign",
							CTO_BegCapsPhraseRule + (8 * i), &ruleOffset, noback, nofor,
							table))
					return 0;
				(*table)->emphRules[MAX_EMPH_CLASSES + i][begPhraseOffset] = ruleOffset;
				return 1;
			}
			case CTO_EndModePhrase: {
				TranslationTableOffset ruleOffset;
				switch (compileBeforeAfter(file)) {
				case 1:	 // before
					if ((*table)->emphRules[MAX_EMPH_CLASSES + i][endPhraseAfterOffset]) {
						compileError(
								file, "Capital sign after last word already defined.");
						return 0;
					}
					// not passing pointer because compileBrailleIndicator may reallocate
					// table
					ruleOffset = (*table)->emphRules[MAX_EMPH_CLASSES + i]
													[endPhraseBeforeOffset];
					if (!compileBrailleIndicator(file, "capital sign before last word",
								CTO_EndCapsPhraseBeforeRule + (8 * i), &ruleOffset,
								noback, nofor, table))
						return 0;
					(*table)->emphRules[MAX_EMPH_CLASSES + i][endPhraseBeforeOffset] =
							ruleOffset;
					return 1;
				case 2:	 // after
					if ((*table)->emphRules[MAX_EMPH_CLASSES + i]
										   [endPhraseBeforeOffset]) {
						compileError(
								file, "Capital sign before last word already defined.");
						return 0;
					}
					// not passing pointer because compileBrailleIndicator may reallocate
					// table
					ruleOffset = (*table)->emphRules[MAX_EMPH_CLASSES + i]
													[endPhraseAfterOffset];
					if (!compileBrailleIndicator(file, "capital sign after last word",
								CTO_EndCapsPhraseAfterRule + (8 * i), &ruleOffset, noback,
								nofor, table))
						return 0;
					(*table)->emphRules[MAX_EMPH_CLASSES + i][endPhraseAfterOffset] =
							ruleOffset;
					return 1;
				default:  // error
					compileError(file, "Invalid lastword indicator location.");
					return 0;
				}
				return 0;
			}
			case CTO_BegMode: {
				// not passing pointer because compileBrailleIndicator may reallocate
				// table
				TranslationTableOffset ruleOffset =
						(*table)->emphRules[MAX_EMPH_CLASSES + i][begOffset];
				if (!compileBrailleIndicator(file, "first letter capital sign",
							CTO_BegCapsRule + (8 * i), &ruleOffset, noback, nofor, table))
					return 0;
				(*table)->emphRules[MAX_EMPH_CLASSES + i][begOffset] = ruleOffset;
				return 1;
			}
			case CTO_EndMode: {
				// not passing pointer because compileBrailleIndicator may reallocate
				// table
				TranslationTableOffset ruleOffset =
						(*table)->emphRules[MAX_EMPH_CLASSES + i][endOffset];
				if (!compileBrailleIndicator(file, "last letter capital sign",
							CTO_EndCapsRule + (8 * i), &ruleOffset, noback, nofor, table))
					return 0;
				(*table)->emphRules[MAX_EMPH_CLASSES + i][endOffset] = ruleOffset;
				return 1;
			}
			case CTO_ModeLetter: {
				// not passing pointer because compileBrailleIndicator may reallocate
				// table
				TranslationTableOffset ruleOffset =
						(*table)->emphRules[MAX_EMPH_CLASSES + i][letterOffset];
				if (!compileBrailleIndicator(file, "single letter capital sign",
							CTO_CapsLetterRule + (8 * i), &ruleOffset, noback, nofor,
							table))
					return 0;
				(*table)->emphRules[MAX_EMPH_CLASSES + i][letterOffset] = ruleOffset;
				return 1;
			}
			case CTO_BegModeWord: {
				// not passing pointer because compileBrailleIndicator may reallocate
				// table
				TranslationTableOffset ruleOffset =
						(*table)->emphRules[MAX_EMPH_CLASSES + i][begWordOffset];
				if (!compileBrailleIndicator(file, "capital word",
							CTO_BegCapsWordRule + (8 * i), &ruleOffset, noback, nofor,
							table))
					return 0;
				(*table)->emphRules[MAX_EMPH_CLASSES + i][begWordOffset] = ruleOffset;
				return 1;
			}
			case CTO_EndModeWord: {
				// not passing pointer because compileBrailleIndicator may reallocate
				// table
				TranslationTableOffset ruleOffset =
						(*table)->emphRules[MAX_EMPH_CLASSES + i][endWordOffset];
				if (!compileBrailleIndicator(file, "capital word stop",
							CTO_EndCapsWordRule + (8 * i), &ruleOffset, noback, nofor,
							table))
					return 0;
				(*table)->emphRules[MAX_EMPH_CLASSES + i][endWordOffset] = ruleOffset;
				return 1;
			}
			case CTO_LenModePhrase:
				return (*table)->emphRules[MAX_EMPH_CLASSES + i][lenPhraseOffset] =
							   compileNumber(file);
			default:
				break;
			}
			break;
		}

		/* these 8 general purpose emphasis opcodes are compiled further down to more
		 * specific internal opcodes:
		 * - emphletter
		 * - begemphword
		 * - endemphword
		 * - begemph
		 * - endemph
		 * - begemphphrase
		 * - endemphphrase
		 * - lenemphphrase
		 */
		case CTO_EmphClass:
			if (!getToken(file, &emphClass, "emphasis class")) {
				compileError(file, "emphclass must be followed by a valid class name.");
				return 0;
			}
			int k, i;
			char *s = malloc(sizeof(char) * (emphClass.length + 1));
			for (k = 0; k < emphClass.length; k++) s[k] = (char)emphClass.chars[k];
			s[k++] = '\0';
			for (i = 0; i < MAX_EMPH_CLASSES && (*table)->emphClassNames[i]; i++)
				if (strcmp(s, (*table)->emphClassNames[i]) == 0) {
					_lou_logMessage(LOU_LOG_WARN, "Duplicate emphasis class: %s", s);
					warningCount++;
					free(s);
					return 1;
				}
			if (i == MAX_EMPH_CLASSES) {
				_lou_logMessage(LOU_LOG_ERROR,
						"Max number of emphasis classes (%i) reached", MAX_EMPH_CLASSES);
				errorCount++;
				free(s);
				return 0;
			}
			switch (i) {
			/* For backwards compatibility (i.e. because programs will assume
			 * the first 3 typeform bits are `italic', `underline' and `bold')
			 * we require that the first 3 emphclass definitions are (in that
			 * order):
			 *
			 *   emphclass italic
			 *   emphclass underline
			 *   emphclass bold
			 *
			 * While it would be possible to use the emphclass opcode only for
			 * defining _additional_ classes (not allowing for them to be called
			 * italic, underline or bold), thereby reducing the amount of
			 * boilerplate, we deliberately choose not to do that in order to
			 * not give italic, underline and bold any special status. The
			 * hope is that eventually all programs will use liblouis for
			 * emphasis the recommended way (i.e. by looking up the supported
			 * typeforms in the documentation or API) so that we can drop this
			 * restriction.
			 */
			case 0:
				if (strcmp(s, "italic") != 0) {
					_lou_logMessage(LOU_LOG_ERROR,
							"First emphasis class must be \"italic\" but got "
							"%s",
							s);
					errorCount++;
					free(s);
					return 0;
				}
				break;
			case 1:
				if (strcmp(s, "underline") != 0) {
					_lou_logMessage(LOU_LOG_ERROR,
							"Second emphasis class must be \"underline\" but "
							"got "
							"%s",
							s);
					errorCount++;
					free(s);
					return 0;
				}
				break;
			case 2:
				if (strcmp(s, "bold") != 0) {
					_lou_logMessage(LOU_LOG_ERROR,
							"Third emphasis class must be \"bold\" but got "
							"%s",
							s);
					errorCount++;
					free(s);
					return 0;
				}
				break;
			}
			(*table)->emphClassNames[i] = s;
			(*table)->emphClasses[i] = (EmphasisClass){ emph_1
						<< i, /* relies on the order of typeforms emph_1..emph_10 */
				0, 0x1 << i, i };
			return 1;
		case CTO_EmphLetter:
		case CTO_BegEmphWord:
		case CTO_EndEmphWord:
		case CTO_BegEmph:
		case CTO_EndEmph:
		case CTO_BegEmphPhrase:
		case CTO_EndEmphPhrase:
		case CTO_LenEmphPhrase:
		case CTO_EmphModeChars:
		case CTO_NoEmphChars: {
			if (!getToken(file, &token, "emphasis class")) return 0;
			if (!parseChars(file, &emphClass, &token)) return 0;
			char *s = malloc(sizeof(char) * (emphClass.length + 1));
			int k, i;
			for (k = 0; k < emphClass.length; k++) s[k] = (char)emphClass.chars[k];
			s[k++] = '\0';
			for (i = 0; i < MAX_EMPH_CLASSES && (*table)->emphClassNames[i]; i++)
				if (strcmp(s, (*table)->emphClassNames[i]) == 0) break;
			if (i == MAX_EMPH_CLASSES || !(*table)->emphClassNames[i]) {
				_lou_logMessage(LOU_LOG_ERROR, "Emphasis class %s not declared", s);
				errorCount++;
				free(s);
				return 0;
			}
			int ok = 0;
			switch (opcode) {
			case CTO_EmphLetter: {
				// not passing pointer because compileBrailleIndicator may reallocate
				// table
				TranslationTableOffset ruleOffset = (*table)->emphRules[i][letterOffset];
				if (!compileBrailleIndicator(file, "single letter",
							CTO_Emph1LetterRule + letterOffset + (8 * i), &ruleOffset,
							noback, nofor, table))
					break;
				(*table)->emphRules[i][letterOffset] = ruleOffset;
				ok = 1;
				break;
			}
			case CTO_BegEmphWord: {
				// not passing pointer because compileBrailleIndicator may reallocate
				// table
				TranslationTableOffset ruleOffset = (*table)->emphRules[i][begWordOffset];
				if (!compileBrailleIndicator(file, "word",
							CTO_Emph1LetterRule + begWordOffset + (8 * i), &ruleOffset,
							noback, nofor, table))
					break;
				(*table)->emphRules[i][begWordOffset] = ruleOffset;
				ok = 1;
				break;
			}
			case CTO_EndEmphWord: {
				// not passing pointer because compileBrailleIndicator may reallocate
				// table
				TranslationTableOffset ruleOffset = (*table)->emphRules[i][endWordOffset];
				if (!compileBrailleIndicator(file, "word stop",
							CTO_Emph1LetterRule + endWordOffset + (8 * i), &ruleOffset,
							noback, nofor, table))
					break;
				(*table)->emphRules[i][endWordOffset] = ruleOffset;
				ok = 1;
				break;
			}
			case CTO_BegEmph: {
				/* fail if both begemph and any of begemphphrase or begemphword are
				 * defined */
				if ((*table)->emphRules[i][begWordOffset] ||
						(*table)->emphRules[i][begPhraseOffset]) {
					compileError(file,
							"Cannot define emphasis for both no context and word or "
							"phrase context, i.e. cannot have both begemph and "
							"begemphword or begemphphrase.");
					break;
				}
				// not passing pointer because compileBrailleIndicator may reallocate
				// table
				TranslationTableOffset ruleOffset = (*table)->emphRules[i][begOffset];
				if (!compileBrailleIndicator(file, "first letter",
							CTO_Emph1LetterRule + begOffset + (8 * i), &ruleOffset,
							noback, nofor, table))
					break;
				(*table)->emphRules[i][begOffset] = ruleOffset;
				ok = 1;
				break;
			}
			case CTO_EndEmph: {
				if ((*table)->emphRules[i][endWordOffset] ||
						(*table)->emphRules[i][endPhraseBeforeOffset] ||
						(*table)->emphRules[i][endPhraseAfterOffset]) {
					compileError(file,
							"Cannot define emphasis for both no context and word or "
							"phrase context, i.e. cannot have both endemph and "
							"endemphword or endemphphrase.");
					break;
				}
				// not passing pointer because compileBrailleIndicator may reallocate
				// table
				TranslationTableOffset ruleOffset = (*table)->emphRules[i][endOffset];
				if (!compileBrailleIndicator(file, "last letter",
							CTO_Emph1LetterRule + endOffset + (8 * i), &ruleOffset,
							noback, nofor, table))
					break;
				(*table)->emphRules[i][endOffset] = ruleOffset;
				ok = 1;
				break;
			}
			case CTO_BegEmphPhrase: {
				// not passing pointer because compileBrailleIndicator may reallocate
				// table
				TranslationTableOffset ruleOffset =
						(*table)->emphRules[i][begPhraseOffset];
				if (!compileBrailleIndicator(file, "first word",
							CTO_Emph1LetterRule + begPhraseOffset + (8 * i), &ruleOffset,
							noback, nofor, table))
					break;
				(*table)->emphRules[i][begPhraseOffset] = ruleOffset;
				ok = 1;
				break;
			}
			case CTO_EndEmphPhrase:
				switch (compileBeforeAfter(file)) {
				case 1: {  // before
					if ((*table)->emphRules[i][endPhraseAfterOffset]) {
						compileError(file, "last word after already defined.");
						break;
					}
					// not passing pointer because compileBrailleIndicator may reallocate
					// table
					TranslationTableOffset ruleOffset =
							(*table)->emphRules[i][endPhraseBeforeOffset];
					if (!compileBrailleIndicator(file, "last word before",
								CTO_Emph1LetterRule + endPhraseBeforeOffset + (8 * i),
								&ruleOffset, noback, nofor, table))
						break;
					(*table)->emphRules[i][endPhraseBeforeOffset] = ruleOffset;
					ok = 1;
					break;
				}
				case 2: {  // after
					if ((*table)->emphRules[i][endPhraseBeforeOffset]) {
						compileError(file, "last word before already defined.");
						break;
					}
					// not passing pointer because compileBrailleIndicator may reallocate
					// table
					TranslationTableOffset ruleOffset =
							(*table)->emphRules[i][endPhraseAfterOffset];
					if (!compileBrailleIndicator(file, "last word after",
								CTO_Emph1LetterRule + endPhraseAfterOffset + (8 * i),
								&ruleOffset, noback, nofor, table))
						break;
					(*table)->emphRules[i][endPhraseAfterOffset] = ruleOffset;
					ok = 1;
					break;
				}
				default:  // error
					compileError(file, "Invalid lastword indicator location.");
					break;
				}
				break;
			case CTO_LenEmphPhrase:
				if (((*table)->emphRules[i][lenPhraseOffset] = compileNumber(file)))
					ok = 1;
				break;
			case CTO_EmphModeChars: {
				if (!getRuleCharsText(file, &ruleChars)) break;
				widechar *emphmodechars = (*table)->emphModeChars[i];
				int len;
				for (len = 0; len < EMPHMODECHARSSIZE && emphmodechars[len]; len++)
					;
				if (len + ruleChars.length > EMPHMODECHARSSIZE) {
					compileError(file, "More than %d characters", EMPHMODECHARSSIZE);
					break;
				}
				ok = 1;
				for (int k = 0; k < ruleChars.length; k++) {
					if (!getChar(ruleChars.chars[k], *table, NULL)) {
						compileError(file, "Emphasis mode character undefined");
						ok = 0;
						break;
					}
					emphmodechars[len++] = ruleChars.chars[k];
				}
				break;
			}
			case CTO_NoEmphChars: {
				if (!getRuleCharsText(file, &ruleChars)) break;
				widechar *noemphchars = (*table)->noEmphChars[i];
				int len;
				for (len = 0; len < NOEMPHCHARSSIZE && noemphchars[len]; len++)
					;
				if (len + ruleChars.length > NOEMPHCHARSSIZE) {
					compileError(file, "More than %d characters", NOEMPHCHARSSIZE);
					break;
				}
				ok = 1;
				for (int k = 0; k < ruleChars.length; k++) {
					if (!getChar(ruleChars.chars[k], *table, NULL)) {
						compileError(file, "Character undefined");
						ok = 0;
						break;
					}
					noemphchars[len++] = ruleChars.chars[k];
				}
				break;
			}
			default:
				break;
			}
			free(s);
			return ok;
		}
		case CTO_LetterSign: {
			// not passing pointer because compileBrailleIndicator may reallocate table
			TranslationTableOffset ruleOffset = (*table)->letterSign;
			if (!compileBrailleIndicator(file, "letter sign", CTO_LetterRule, &ruleOffset,
						noback, nofor, table))
				return 0;
			(*table)->letterSign = ruleOffset;
			return 1;
		}
		case CTO_NoLetsignBefore:
			if (!getRuleCharsText(file, &ruleChars)) return 0;
			if (((*table)->noLetsignBeforeCount + ruleChars.length) > LETSIGNBEFORESIZE) {
				compileError(file, "More than %d characters", LETSIGNBEFORESIZE);
				return 0;
			}
			for (int k = 0; k < ruleChars.length; k++)
				(*table)->noLetsignBefore[(*table)->noLetsignBeforeCount++] =
						ruleChars.chars[k];
			return 1;
		case CTO_NoLetsign:
			if (!getRuleCharsText(file, &ruleChars)) return 0;
			if (((*table)->noLetsignCount + ruleChars.length) > LETSIGNSIZE) {
				compileError(file, "More than %d characters", LETSIGNSIZE);
				return 0;
			}
			for (int k = 0; k < ruleChars.length; k++)
				(*table)->noLetsign[(*table)->noLetsignCount++] = ruleChars.chars[k];
			return 1;
		case CTO_NoLetsignAfter:
			if (!getRuleCharsText(file, &ruleChars)) return 0;
			if (((*table)->noLetsignAfterCount + ruleChars.length) > LETSIGNAFTERSIZE) {
				compileError(file, "More than %d characters", LETSIGNAFTERSIZE);
				return 0;
			}
			for (int k = 0; k < ruleChars.length; k++)
				(*table)->noLetsignAfter[(*table)->noLetsignAfterCount++] =
						ruleChars.chars[k];
			return 1;
		case CTO_NumberSign: {
			// not passing pointer because compileBrailleIndicator may reallocate table
			TranslationTableOffset ruleOffset = (*table)->numberSign;
			if (!compileBrailleIndicator(file, "number sign", CTO_NumberRule, &ruleOffset,
						noback, nofor, table))
				return 0;
			(*table)->numberSign = ruleOffset;
			return 1;
		}

		case CTO_NumericModeChars:
			if (!getRuleCharsText(file, &ruleChars)) return 0;
			for (int k = 0; k < ruleChars.length; k++) {
				TranslationTableCharacter *c = getChar(ruleChars.chars[k], *table, NULL);
				if (!c) {
					compileError(file, "Numeric mode character undefined: %s",
							_lou_showString(&ruleChars.chars[k], 1, 0));
					return 0;
				}
				c->attributes |= CTC_NumericMode;
				(*table)->usesNumericMode = 1;
			}
			return 1;

		case CTO_MidEndNumericModeChars:
			if (!getRuleCharsText(file, &ruleChars)) return 0;
			for (int k = 0; k < ruleChars.length; k++) {
				TranslationTableCharacter *c = getChar(ruleChars.chars[k], *table, NULL);
				if (!c) {
					compileError(file, "Midendnumeric mode character undefined");
					return 0;
				}
				c->attributes |= CTC_MidEndNumericMode;
				(*table)->usesNumericMode = 1;
			}
			return 1;

		case CTO_NumericNoContractChars:
			if (!getRuleCharsText(file, &ruleChars)) return 0;
			for (int k = 0; k < ruleChars.length; k++) {
				TranslationTableCharacter *c = getChar(ruleChars.chars[k], *table, NULL);
				if (!c) {
					compileError(file, "Numeric no contraction character undefined");
					return 0;
				}
				c->attributes |= CTC_NumericNoContract;
				(*table)->usesNumericMode = 1;
			}
			return 1;

		case CTO_NoContractSign: {
			// not passing pointer because compileBrailleIndicator may reallocate table
			TranslationTableOffset ruleOffset = (*table)->noContractSign;
			if (!compileBrailleIndicator(file, "no contractions sign", CTO_NoContractRule,
						&ruleOffset, noback, nofor, table))
				return 0;
			(*table)->noContractSign = ruleOffset;
			return 1;
		}
		case CTO_SeqDelimiter:
			if (!getRuleCharsText(file, &ruleChars)) return 0;
			for (int k = 0; k < ruleChars.length; k++) {
				TranslationTableCharacter *c = getChar(ruleChars.chars[k], *table, NULL);
				if (!c) {
					compileError(file, "Sequence delimiter character undefined");
					return 0;
				}
				c->attributes |= CTC_SeqDelimiter;
				(*table)->usesSequences = 1;
			}
			return 1;

		case CTO_SeqBeforeChars:
			if (!getRuleCharsText(file, &ruleChars)) return 0;
			for (int k = 0; k < ruleChars.length; k++) {
				TranslationTableCharacter *c = getChar(ruleChars.chars[k], *table, NULL);
				if (!c) {
					compileError(file, "Sequence before character undefined");
					return 0;
				}
				c->attributes |= CTC_SeqBefore;
			}
			return 1;

		case CTO_SeqAfterChars:
			if (!getRuleCharsText(file, &ruleChars)) return 0;
			for (int k = 0; k < ruleChars.length; k++) {
				TranslationTableCharacter *c = getChar(ruleChars.chars[k], *table, NULL);
				if (!c) {
					compileError(file, "Sequence after character undefined");
					return 0;
				}
				c->attributes |= CTC_SeqAfter;
			}
			return 1;

		case CTO_SeqAfterPattern:
			if (!getRuleCharsText(file, &ruleChars)) return 0;
			if (((*table)->seqPatternsCount + ruleChars.length + 1) > SEQPATTERNSIZE) {
				compileError(file, "More than %d characters", SEQPATTERNSIZE);
				return 0;
			}
			for (int k = 0; k < ruleChars.length; k++)
				(*table)->seqPatterns[(*table)->seqPatternsCount++] = ruleChars.chars[k];
			(*table)->seqPatterns[(*table)->seqPatternsCount++] = 0;
			return 1;

		case CTO_SeqAfterExpression:
			if (!getRuleCharsText(file, &ruleChars)) return 0;
			for ((*table)->seqAfterExpressionLength = 0;
					(*table)->seqAfterExpressionLength < ruleChars.length;
					(*table)->seqAfterExpressionLength++)
				(*table)->seqAfterExpression[(*table)->seqAfterExpressionLength] =
						ruleChars.chars[(*table)->seqAfterExpressionLength];
			(*table)->seqAfterExpression[(*table)->seqAfterExpressionLength] = 0;
			return 1;

		case CTO_CapsModeChars:
			if (!getRuleCharsText(file, &ruleChars)) return 0;
			for (int k = 0; k < ruleChars.length; k++) {
				TranslationTableCharacter *c = getChar(ruleChars.chars[k], *table, NULL);
				if (!c) {
					compileError(file, "Capital mode character undefined");
					return 0;
				}
				c->attributes |= CTC_CapsMode;
				(*table)->hasCapsModeChars = 1;
			}
			return 1;

		case CTO_BegComp: {
			// not passing pointer because compileBrailleIndicator may reallocate table
			TranslationTableOffset ruleOffset = (*table)->begComp;
			if (!compileBrailleIndicator(file, "begin computer braille", CTO_BegCompRule,
						&ruleOffset, noback, nofor, table))
				return 0;
			(*table)->begComp = ruleOffset;
			return 1;
		}
		case CTO_EndComp: {
			// not passing pointer because compileBrailleIndicator may reallocate table
			TranslationTableOffset ruleOffset = (*table)->endComp;
			if (!compileBrailleIndicator(file, "end computer braslle", CTO_EndCompRule,
						&ruleOffset, noback, nofor, table))
				return 0;
			(*table)->endComp = ruleOffset;
			return 1;
		}
		case CTO_NoCross:
			if (nocross) {
				compileError(
						file, "%s already specified.", _lou_findOpcodeName(CTO_NoCross));
				return 0;
			}
			nocross = 1;
			goto doOpcode;
		case CTO_Syllable:
			(*table)->syllables = 1;
		case CTO_Always:
		case CTO_LargeSign:
		case CTO_WholeWord:
		case CTO_PartWord:
		case CTO_JoinNum:
		case CTO_JoinableWord:
		case CTO_LowWord:
		case CTO_SuffixableWord:
		case CTO_PrefixableWord:
		case CTO_BegWord:
		case CTO_BegMidWord:
		case CTO_MidWord:
		case CTO_MidEndWord:
		case CTO_EndWord:
		case CTO_PrePunc:
		case CTO_PostPunc:
		case CTO_BegNum:
		case CTO_MidNum:
		case CTO_EndNum:
		case CTO_Repeated:
		case CTO_RepWord:
			if (!getRuleCharsText(file, &ruleChars)) return 0;
			if (!getRuleDotsPattern(file, &ruleDots)) return 0;
			if (ruleDots.length == 0)
				// check that all characters in a rule with `=` as second operand are
				// defined (or based on another character)
				for (int k = 0; k < ruleChars.length; k++) {
					TranslationTableCharacter *c =
							getChar(ruleChars.chars[k], *table, NULL);
					if (!(c && (c->definitionRule || c->basechar))) {
						compileError(file, "Character %s is not defined",
								_lou_showString(&ruleChars.chars[k], 1, 0));
						return 0;
					}
				}
			TranslationTableRule *r;
			if (!addRule(file, opcode, &ruleChars, &ruleDots, after, before, NULL, &r,
						noback, nofor, table))
				return 0;
			if (nocross) r->nocross = 1;
			return 1;
			// if (opcode == CTO_MidNum)
			// {
			//   TranslationTableCharacter *c = getChar(ruleChars.chars[0]);
			//   if(c)
			//     c->attributes |= CTC_NumericMode;
			// }
		case CTO_RepEndWord:
			if (!getRuleCharsText(file, &ruleChars)) return 0;
			CharsString dots;
			if (!getToken(file, &dots, "dots,dots operand")) return 0;
			int len = dots.length;
			for (int k = 0; k < len - 1; k++) {
				if (dots.chars[k] == ',') {
					dots.length = k;
					if (!parseDots(file, &ruleDots, &dots)) return 0;
					ruleDots.chars[ruleDots.length++] = ',';
					k++;
					if (k == len - 1 && dots.chars[k] == '=') {
						// check that all characters are defined (or based on another
						// character)
						for (int l = 0; l < ruleChars.length; l++) {
							TranslationTableCharacter *c =
									getChar(ruleChars.chars[l], *table, NULL);
							if (!(c && (c->definitionRule || c->basechar))) {
								compileError(file, "Character %s is not defined",
										_lou_showString(&ruleChars.chars[l], 1, 0));
								return 0;
							}
						}
					} else {
						CharsString x, y;
						x.length = 0;
						while (k < len) x.chars[x.length++] = dots.chars[k++];
						if (parseDots(file, &y, &x))
							for (int l = 0; l < y.length; l++)
								ruleDots.chars[ruleDots.length++] = y.chars[l];
					}
					return addRule(file, opcode, &ruleChars, &ruleDots, after, before,
							NULL, NULL, noback, nofor, table);
				}
			}
			return 0;
		case CTO_CompDots:
		case CTO_Comp6: {
			TranslationTableOffset ruleOffset;
			if (!getRuleCharsText(file, &ruleChars)) return 0;
			if (ruleChars.length != 1) {
				compileError(file, "first operand must be 1 character");
				return 0;
			}
			if (nofor || noback) {
				compileWarning(file, "nofor and noback not allowed on comp6 rules");
			}
			if (!getRuleDotsPattern(file, &ruleDots)) return 0;
			if (!addRule(file, opcode, &ruleChars, &ruleDots, after, before, &ruleOffset,
						NULL, noback, nofor, table))
				return 0;
			return 1;
		}
		case CTO_ExactDots:
			if (!getRuleCharsText(file, &ruleChars)) return 0;
			if (ruleChars.chars[0] != '@') {
				compileError(file, "The operand must begin with an at sign (@)");
				return 0;
			}
			for (int k = 1; k < ruleChars.length; k++)
				scratchPad.chars[k - 1] = ruleChars.chars[k];
			scratchPad.length = ruleChars.length - 1;
			if (!parseDots(file, &ruleDots, &scratchPad)) return 0;
			return addRule(file, opcode, &ruleChars, &ruleDots, before, after, NULL, NULL,
					noback, nofor, table);
		case CTO_CapsNoCont: {
			TranslationTableOffset ruleOffset;
			ruleChars.length = 1;
			ruleChars.chars[0] = 'a';
			if (!addRule(file, CTO_CapsNoContRule, &ruleChars, NULL, after, before,
						&ruleOffset, NULL, noback, nofor, table))
				return 0;
			(*table)->capsNoCont = ruleOffset;
			return 1;
		}
		case CTO_Replace:
			if (getRuleCharsText(file, &ruleChars)) {
				if (atEndOfLine(file))
					ruleDots.length = ruleDots.chars[0] = 0;
				else {
					getRuleDotsText(file, &ruleDots);
					if (ruleDots.chars[0] == '#')
						ruleDots.length = ruleDots.chars[0] = 0;
					else if (ruleDots.chars[0] == '\\' && ruleDots.chars[1] == '#')
						memmove(&ruleDots.chars[0], &ruleDots.chars[1],
								ruleDots.length-- * CHARSIZE);
				}
			}
			for (int k = 0; k < ruleChars.length; k++)
				putChar(file, ruleChars.chars[k], table, NULL);
			for (int k = 0; k < ruleDots.length; k++)
				putChar(file, ruleDots.chars[k], table, NULL);
			return addRule(file, opcode, &ruleChars, &ruleDots, after, before, NULL, NULL,
					noback, nofor, table);
		case CTO_Correct:
			(*table)->corrections = 1;
			goto doPass;
		case CTO_Pass2:
			if ((*table)->numPasses < 2) (*table)->numPasses = 2;
			goto doPass;
		case CTO_Pass3:
			if ((*table)->numPasses < 3) (*table)->numPasses = 3;
			goto doPass;
		case CTO_Pass4:
			if ((*table)->numPasses < 4) (*table)->numPasses = 4;
		doPass:
		case CTO_Context:
			if (!(nofor || noback)) {
				compileError(file, "%s or %s must be specified.",
						_lou_findOpcodeName(CTO_NoFor), _lou_findOpcodeName(CTO_NoBack));
				return 0;
			}
			return compilePassOpcode(file, opcode, noback, nofor, table);
		case CTO_Contraction:
		case CTO_NoCont:
		case CTO_CompBrl:
		case CTO_Literal:
			if (!getRuleCharsText(file, &ruleChars)) return 0;
			// check that all characters in a compbrl, contraction,
			// nocont or literal rule are defined (or based on another
			// character)
			for (int k = 0; k < ruleChars.length; k++) {
				TranslationTableCharacter *c = getChar(ruleChars.chars[k], *table, NULL);
				if (!(c && (c->definitionRule || c->basechar))) {
					compileError(file, "Character %s is not defined",
							_lou_showString(&ruleChars.chars[k], 1, 0));
					return 0;
				}
			}
			return addRule(file, opcode, &ruleChars, NULL, after, before, NULL, NULL,
					noback, nofor, table);
		case CTO_MultInd: {
			ruleChars.length = 0;
			if (!getToken(file, &token, "multiple braille indicators") ||
					!parseDots(file, &cells, &token))
				return 0;
			while (getToken(file, &token, "multind opcodes")) {
				opcode = getOpcode(file, &token);
				if (opcode == CTO_None) {
					compileError(file, "opcode %s not defined.",
							_lou_showString(token.chars, token.length, 0));
					return 0;
				}
				if (!(opcode >= CTO_CapsLetter && opcode < CTO_MultInd)) {
					compileError(file, "Not a braille indicator opcode.");
					return 0;
				}
				ruleChars.chars[ruleChars.length++] = (widechar)opcode;
				if (atEndOfLine(file)) break;
			}
			return addRule(file, CTO_MultInd, &ruleChars, &cells, after, before, NULL,
					NULL, noback, nofor, table);
		}

		case CTO_Class:
			compileWarning(file, "class is deprecated, use attribute instead");
		case CTO_Attribute: {
			if (nofor || noback) {
				compileWarning(
						file, "nofor and noback not allowed before class/attribute");
			}
			if ((opcode == CTO_Class && (*table)->usesAttributeOrClass == 1) ||
					(opcode == CTO_Attribute && (*table)->usesAttributeOrClass == 2)) {
				compileError(file,
						"attribute and class rules must not be both present in a table");
				return 0;
			}
			if (opcode == CTO_Class)
				(*table)->usesAttributeOrClass = 2;
			else
				(*table)->usesAttributeOrClass = 1;
			if (!getToken(file, &token, "attribute name")) {
				compileError(file, "Expected %s", "attribute name");
				return 0;
			}
			if (!(*table)->characterClasses && !allocateCharacterClasses(*table)) {
				return 0;
			}

			TranslationTableCharacterAttributes attribute = 0;
			{
				int attrNumber = -1;
				switch (token.chars[0]) {
				case '0':
				case '1':
				case '2':
				case '3':
				case '4':
				case '5':
				case '6':
				case '7':
				case '8':
				case '9':
					attrNumber = token.chars[0] - '0';
					break;
				}
				if (attrNumber >= 0) {
					if (opcode == CTO_Class) {
						compileError(file,
								"Invalid class name: may not contain digits, use "
								"attribute instead of class");
						return 0;
					}
					if (token.length > 1 || attrNumber > 7) {
						compileError(file,
								"Invalid attribute name: must be a digit between 0 and 7 "
								"or a word containing only letters");
						return 0;
					}
					if (!(*table)->numberedAttributes[attrNumber])
						// attribute not used before yet: assign it a value
						(*table)->numberedAttributes[attrNumber] =
								getNextNumberedAttribute(*table);
					attribute = (*table)->numberedAttributes[attrNumber];
				} else {
					const CharacterClass *namedAttr = findCharacterClass(&token, *table);
					if (!namedAttr) {
						// no class with that name: create one
						namedAttr = addCharacterClass(
								file, &token.chars[0], token.length, *table, 1);
						if (!namedAttr) return 0;
					}
					// there is a class with that name or a new class was successfully
					// created
					attribute = namedAttr->attribute;
					if (attribute == CTC_UpperCase || attribute == CTC_LowerCase)
						attribute |= CTC_Letter;
				}
			}
			CharsString characters;
			if (!getCharacters(file, &characters)) return 0;
			for (int i = 0; i < characters.length; i++) {
				// get the character from the table, or if it is not defined yet,
				// define it
				TranslationTableCharacter *character =
						putChar(file, characters.chars[i], table, NULL);
				// set the attribute
				character->attributes |= attribute;
				// also set the attribute on the associated dots (if any)
				if (character->basechar)
					character = (TranslationTableCharacter *)&(*table)
										->ruleArea[character->basechar];
				if (character->definitionRule) {
					TranslationTableRule *defRule =
							(TranslationTableRule *)&(*table)
									->ruleArea[character->definitionRule];
					if (defRule->dotslen == 1) {
						TranslationTableCharacter *dots =
								getDots(defRule->charsdots[defRule->charslen], *table);
						if (dots) dots->attributes |= attribute;
					}
				}
			}
			return 1;
		}

			{
				TranslationTableCharacterAttributes *attributes;
				const CharacterClass *class;
			case CTO_After:
				attributes = &after;
				goto doBeforeAfter;
			case CTO_Before:
				attributes = &before;
			doBeforeAfter:
				if (!(*table)->characterClasses) {
					if (!allocateCharacterClasses(*table)) return 0;
				}
				if (!getToken(file, &token, "attribute name")) return 0;
				if (!(class = findCharacterClass(&token, *table))) {
					compileError(file, "attribute not defined");
					return 0;
				}
				*attributes |= class->attribute;
				goto doOpcode;
			}
		case CTO_Base:
			if (nofor || noback) {
				compileWarning(file, "nofor and noback not allowed before base");
			}
			if (!getToken(file, &token, "attribute name")) {
				compileError(
						file, "base opcode must be followed by a valid attribute name.");
				return 0;
			}
			if (!(*table)->characterClasses && !allocateCharacterClasses(*table)) {
				return 0;
			}
			const CharacterClass *mode = findCharacterClass(&token, *table);
			if (!mode) {
				mode = addCharacterClass(file, token.chars, token.length, *table, 1);
				if (!mode) return 0;
			}
			if (!(mode->attribute == CTC_UpperCase || mode->attribute == CTC_Digit) &&
					mode->attribute >= CTC_Space && mode->attribute <= CTC_LitDigit) {
				compileError(file,
						"base opcode must be followed by \"uppercase\", \"digit\", or a "
						"custom attribute name.");
				return 0;
			}
			if (!getRuleCharsText(file, &token)) return 0;
			if (token.length != 1) {
				compileError(file,
						"Exactly one character followed by one base character is "
						"required.");
				return 0;
			}
			TranslationTableOffset characterOffset;
			TranslationTableCharacter *character =
					putChar(file, token.chars[0], table, &characterOffset);
			if (!getRuleCharsText(file, &token)) return 0;
			if (token.length != 1) {
				compileError(file, "Exactly one base character is required.");
				return 0;
			}
			if (character->definitionRule) {
				TranslationTableRule *prevRule =
						(TranslationTableRule *)&(*table)
								->ruleArea[character->definitionRule];
				_lou_logMessage(LOU_LOG_DEBUG,
						"%s:%d: Character already defined (%s). The base rule will take "
						"precedence.",
						file->fileName, file->lineNumber,
						printSource(file, prevRule->sourceFile, prevRule->sourceLine));
				character->definitionRule = 0;
			}
			TranslationTableOffset basechar;
			putChar(file, token.chars[0], table, &basechar);
			// putChar may have moved table, so make sure character is still valid
			character = (TranslationTableCharacter *)&(*table)->ruleArea[characterOffset];
			if (character->basechar) {
				if (character->basechar == basechar &&
						character->mode == mode->attribute) {
					_lou_logMessage(LOU_LOG_DEBUG, "%s:%d: Duplicate base rule.",
							file->fileName, file->lineNumber);
				} else {
					_lou_logMessage(LOU_LOG_DEBUG,
							"%s:%d: A different base rule already exists for this "
							"character (%s). The new rule will take precedence.",
							file->fileName, file->lineNumber,
							printSource(
									file, character->sourceFile, character->sourceLine));
				}
			}
			character->basechar = basechar;
			character->mode = mode->attribute;
			character->sourceFile = file->sourceFile;
			character->sourceLine = file->lineNumber;
			/* some other processing is done at the end of the compilation, in
			 * finalizeTable() */
			return 1;
		case CTO_EmpMatchBefore:
			before |= CTC_EmpMatch;
			goto doOpcode;
		case CTO_EmpMatchAfter:
			after |= CTC_EmpMatch;
			goto doOpcode;

		case CTO_SwapCc:
		case CTO_SwapCd:
		case CTO_SwapDd:
			return compileSwap(file, opcode, noback, nofor, table);
		case CTO_Hyphen:
		case CTO_DecPoint:
			//	case CTO_Apostrophe:
			//	case CTO_Initial:
			if (!getRuleCharsText(file, &ruleChars)) return 0;
			if (!getRuleDotsPattern(file, &ruleDots)) return 0;
			if (ruleChars.length != 1 || ruleDots.length < 1) {
				compileError(file,
						"One Unicode character and at least one cell are "
						"required.");
				return 0;
			}
			return addRule(file, opcode, &ruleChars, &ruleDots, after, before, NULL, NULL,
					noback, nofor, table);
			// if (opcode == CTO_DecPoint)
			// {
			//   TranslationTableCharacter *c =
			//   getChar(ruleChars.chars[0]);
			//   if(c)
			//     c->attributes |= CTC_NumericMode;
			// }
		default:
			compileError(file, "unimplemented opcode.");
			return 0;
		}
	}
	return 0;
}

Prevent an invalid memory writes in compileRule

Thanks to Han Zheng for reporting it

Fixes #1214