/* Serial interface for local domain connections on Un*x like systems.

   Copyright (C) 1992-2025 Free Software Foundation, Inc.

   This file is part of GDB.

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 3 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */

#include "serial.h"
#include "ser-base.h"

#include <sys/socket.h>
#include <sys/un.h>

#ifndef UNIX_PATH_MAX
#define UNIX_PATH_MAX sizeof(((struct sockaddr_un *) NULL)->sun_path)
#endif

/* Open an AF_UNIX socket.  */

static void
uds_open (struct serial *scb, const char *name)
{
  struct sockaddr_un addr;

  if (strlen (name) > UNIX_PATH_MAX - 1)
    error (_("The socket name is too long.  It may be no longer than %s bytes."),
	   pulongest (UNIX_PATH_MAX - 1L));

  memset (&addr, 0, sizeof addr);
  addr.sun_family = AF_UNIX;
  strncpy (addr.sun_path, name, UNIX_PATH_MAX - 1);

  scb->fd = socket (AF_UNIX, SOCK_STREAM, 0);
  if (scb->fd < 0)
    perror_with_name (_("could not open socket"));

  if (connect (scb->fd, (struct sockaddr *) &addr,
	       sizeof (struct sockaddr_un)) < 0)
    {
      int saved = errno;
      close (scb->fd);
      perror_with_name (_("could not connect to remote"), saved);
    }
}

static void
uds_close (struct serial *scb)
{
  if (scb->fd == -1)
    return;

  close (scb->fd);
  scb->fd = -1;
}

static int
uds_read_prim (struct serial *scb, size_t count)
{
  int result = recv (scb->fd, scb->buf, count, 0);
  if (result == -1 && errno != EINTR)
    perror_with_name ("error while reading");
  return result;
}

static int
uds_write_prim (struct serial *scb, const void *buf, size_t count)
{
  int result = send (scb->fd, buf, count, 0);
  if (result == -1 && errno != EINTR)
    perror_with_name ("error while writing");
  return result;
}

/* The local socket ops.  */

static const struct serial_ops uds_ops =
{
  "local",
  uds_open,
  uds_close,
  NULL,
  ser_base_readchar,
  ser_base_write,
  ser_base_flush_output,
  ser_base_flush_input,
  ser_base_send_break,
  ser_base_raw,
  ser_base_get_tty_state,
  ser_base_copy_tty_state,
  ser_base_set_tty_state,
  ser_base_print_tty_state,
  ser_base_setbaudrate,
  ser_base_setstopbits,
  ser_base_setparity,
  ser_base_drain_output,
  ser_base_async,
  uds_read_prim,
  uds_write_prim
};

INIT_GDB_FILE (ser_socket)
{
  serial_add_interface (&uds_ops);
}
