1/*
2 * Copyright (c) 2022 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15#include <errno.h>
16#include <fcntl.h>
17#include <stdlib.h>
18#include <stdio.h>
19#include <sys/stat.h>
20#include <termios.h>
21#include <unistd.h>
22
23#include "beget_ext.h"
24#include "control_fd.h"
25#include "securec.h"
26
27CallbackSendMsgProcess g_sendMsg = NULL;
28
29CONTROL_FD_STATIC void ProcessPtyWrite(const WatcherHandle taskHandle, int fd, uint32_t *events, const void *context)
30{
31    if ((fd < 0) || (events == NULL) || (context == NULL)) {
32        BEGET_LOGE("[control_fd] Invalid fifo write parameter");
33        return;
34    }
35    CmdAgent *agent = (CmdAgent *)context;
36    char rbuf[PTY_BUF_SIZE] = {0};
37    ssize_t rlen = read(fd, rbuf, PTY_BUF_SIZE - 1);
38    int ret = fflush(stdin);
39    BEGET_ERROR_CHECK(ret == 0, return, "[control_fd] Failed fflush err=%d", errno);
40    if (rlen > 0) {
41        ssize_t wlen = write(agent->ptyFd, rbuf, rlen);
42        BEGET_ERROR_CHECK(wlen == rlen, return, "[control_fd] Failed write fifo err=%d", errno);
43    }
44    ret = fflush(stdout);
45    BEGET_ERROR_CHECK(ret == 0, return, "[control_fd] Failed fflush err=%d", errno);
46    *events = EVENT_READ;
47}
48
49CONTROL_FD_STATIC void ProcessPtyRead(const WatcherHandle taskHandle, int fd, uint32_t *events, const void *context)
50{
51    if ((fd < 0) || (events == NULL) || (context == NULL)) {
52        BEGET_LOGE("[control_fd] Invalid fifo read parameter");
53        return;
54    }
55    CmdAgent *agent = (CmdAgent *)context;
56    char buf[PTY_BUF_SIZE] = {0};
57    ssize_t readlen = 0;
58    do {
59        readlen = read(fd, buf, PTY_BUF_SIZE - 1);
60    } while (readlen == -1 && errno == EINTR);
61
62    if (readlen > 0) {
63        fprintf(stdout, "%s", buf);
64    } else {
65        (void)close(agent->ptyFd);
66        LE_StopLoop(LE_GetDefaultLoop());
67        *events = 0;
68        return;
69    }
70    int ret = fflush(stdout);
71    BEGET_ERROR_CHECK(ret == 0, return, "[control_fd] Failed fflush err=%d", errno);
72    *events = EVENT_READ;
73}
74
75CONTROL_FD_STATIC void CmdClientOnRecvMessage(const TaskHandle task, const uint8_t *buffer, uint32_t buffLen)
76{
77    BEGET_LOGI("[control_fd] CmdOnRecvMessage %s len %d.", (char *)buffer, buffLen);
78}
79
80CONTROL_FD_STATIC void CmdOnConnectComplete(const TaskHandle client)
81{
82    BEGET_LOGI("[control_fd] CmdOnConnectComplete");
83}
84
85CONTROL_FD_STATIC void CmdOnClose(const TaskHandle task)
86{
87    BEGET_LOGI("[control_fd] CmdOnClose");
88    CmdAgent *agent = (CmdAgent *)LE_GetUserData(task);
89    BEGET_ERROR_CHECK(agent != NULL, return, "[control_fd] Invalid agent");
90    (void)close(agent->ptyFd);
91    agent->ptyFd = -1;
92    LE_StopLoop(LE_GetDefaultLoop());
93}
94
95CONTROL_FD_STATIC void CmdDisConnectComplete(const TaskHandle client)
96{
97    BEGET_LOGI("[control_fd] CmdDisConnectComplete");
98}
99
100CONTROL_FD_STATIC void CmdOnSendMessageComplete(const TaskHandle task, const BufferHandle handle)
101{
102    BEGET_LOGI("[control_fd] CmdOnSendMessageComplete");
103}
104
105CONTROL_FD_STATIC CmdAgent *CmdAgentCreate(const char *server)
106{
107    if (server == NULL) {
108        BEGET_LOGE("[control_fd] Invalid parameter");
109        return NULL;
110    }
111    TaskHandle task = NULL;
112    LE_StreamInfo info = {};
113    info.baseInfo.flags = TASK_STREAM | TASK_PIPE | TASK_CONNECT;
114    info.server = (char *)server;
115    info.baseInfo.userDataSize = sizeof(CmdAgent);
116    info.baseInfo.close = CmdOnClose;
117    info.disConnectComplete = CmdDisConnectComplete;
118    info.connectComplete = CmdOnConnectComplete;
119    info.sendMessageComplete = CmdOnSendMessageComplete;
120    info.recvMessage = CmdClientOnRecvMessage;
121    LE_STATUS status = LE_CreateStreamClient(LE_GetDefaultLoop(), &task, &info);
122    BEGET_ERROR_CHECK(status == 0, return NULL, "[control_fd] Failed create client");
123    CmdAgent *agent = (CmdAgent *)LE_GetUserData(task);
124    BEGET_ERROR_CHECK(agent != NULL, return NULL, "[control_fd] Invalid agent");
125    agent->task = task;
126    return agent;
127}
128
129CONTROL_FD_STATIC int SendCmdMessage(const CmdAgent *agent, uint16_t type, const char *cmd, const char *ptyName)
130{
131    if ((agent == NULL) || (cmd == NULL) || (ptyName == NULL)) {
132        BEGET_LOGE("[control_fd] Invalid parameter");
133        return -1;
134    }
135    BufferHandle handle = NULL;
136    uint32_t bufferSize = sizeof(CmdMessage) + strlen(cmd) + PTY_PATH_SIZE + 1;
137    handle = LE_CreateBuffer(LE_GetDefaultLoop(), bufferSize);
138    char *buff = (char *)LE_GetBufferInfo(handle, NULL, NULL);
139    BEGET_ERROR_CHECK(buff != NULL, return -1, "[control_fd] Failed get buffer info");
140    CmdMessage *message = (CmdMessage *)buff;
141    message->msgSize = bufferSize;
142    message->type = type;
143    int ret = strcpy_s(message->ptyName, PTY_PATH_SIZE - 1, ptyName);
144    BEGET_ERROR_CHECK(ret == 0, LE_FreeBuffer(LE_GetDefaultLoop(), agent->task, handle);
145        return -1, "[control_fd] Failed to copy pty name %s", ptyName);
146    ret = strcpy_s(message->cmd, bufferSize - sizeof(CmdMessage) - PTY_PATH_SIZE, cmd);
147    BEGET_ERROR_CHECK(ret == 0, LE_FreeBuffer(LE_GetDefaultLoop(), agent->task, handle);
148        return -1, "[control_fd] Failed to copy cmd %s", cmd);
149    ret = LE_Send(LE_GetDefaultLoop(), agent->task, handle, bufferSize);
150    BEGET_ERROR_CHECK(ret == 0, return -1, "[control_fd] Failed LE_Send msg type %d, cmd %s",
151        message->type, message->cmd);
152    return 0;
153}
154
155int InitPtyInterface(CmdAgent *agent, uint16_t type, const char *cmd, CallbackSendMsgProcess callback)
156{
157    if ((cmd == NULL) || (agent == NULL)) {
158        return -1;
159    }
160#ifndef STARTUP_INIT_TEST
161    g_sendMsg = callback;
162    // initialize terminal
163    struct termios term;
164    int ret = tcgetattr(STDIN_FILENO, &term);
165    BEGET_ERROR_CHECK(ret == 0, return -1, "Failed tcgetattr stdin, err=%d", errno);
166    cfmakeraw(&term);
167    term.c_cc[VTIME] = 0;
168    term.c_cc[VMIN] = 1;
169    ret = tcsetattr(STDIN_FILENO, TCSANOW, &term);
170    BEGET_ERROR_CHECK(ret == 0, return -1, "Failed tcsetattr term, err=%d", errno);
171    // open master pty and get slave pty
172    int pfd = open("/dev/ptmx", O_RDWR | O_CLOEXEC);
173    BEGET_ERROR_CHECK(pfd >= 0, return -1, "Failed open pty err=%d", errno);
174    BEGET_ERROR_CHECK(grantpt(pfd) >= 0, close(pfd); return -1, "Failed to call grantpt");
175    BEGET_ERROR_CHECK(unlockpt(pfd) >= 0, close(pfd); return -1, "Failed to call unlockpt");
176    char ptsbuffer[PTY_PATH_SIZE] = {0};
177    ret = ptsname_r(pfd, ptsbuffer, sizeof(ptsbuffer));
178    BEGET_ERROR_CHECK(ret >= 0, close(pfd); return -1, "Failed to get pts name err=%d", errno);
179    BEGET_LOGI("ptsbuffer is %s", ptsbuffer);
180    BEGET_ERROR_CHECK(chmod(ptsbuffer, S_IRWXU | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH) == 0,
181        close(pfd); return -1, "Failed to chmod %s, err=%d", ptsbuffer, errno);
182    agent->ptyFd = pfd;
183
184    LE_WatchInfo info = {};
185    info.flags = 0;
186    info.events = EVENT_READ;
187    info.processEvent = ProcessPtyRead;
188    info.fd = pfd; // read ptmx
189    BEGET_ERROR_CHECK(LE_StartWatcher(LE_GetDefaultLoop(), &agent->reader, &info, agent) == LE_SUCCESS,
190        close(pfd); return -1, "[control_fd] Failed le_loop start watcher ptmx read");
191    info.processEvent = ProcessPtyWrite;
192    info.fd = STDIN_FILENO; // read stdin and write ptmx
193    BEGET_ERROR_CHECK(LE_StartWatcher(LE_GetDefaultLoop(), &agent->input, &info, agent) == LE_SUCCESS,
194        close(pfd); return -1, "[control_fd] Failed le_loop start watcher stdin read and write ptmx");
195    if (g_sendMsg == NULL) {
196        ret = SendCmdMessage(agent, type, cmd, ptsbuffer);
197    } else {
198        ret = g_sendMsg(agent, type, cmd, ptsbuffer);
199    }
200    BEGET_ERROR_CHECK(ret == 0, close(pfd); return -1, "[control_fd] Failed send message");
201#endif
202    return 0;
203}
204
205void CmdClientInit(const char *socketPath, uint16_t type, const char *cmd, CallbackSendMsgProcess callback)
206{
207    if ((socketPath == NULL) || (cmd == NULL)) {
208        BEGET_LOGE("[control_fd] Invalid parameter");
209    }
210    BEGET_LOGI("[control_fd] CmdAgentInit");
211    CmdAgent *agent = CmdAgentCreate(socketPath);
212    BEGET_ERROR_CHECK(agent != NULL, return, "[control_fd] Failed to create agent");
213#ifndef STARTUP_INIT_TEST
214    int ret = InitPtyInterface(agent, type, cmd, callback);
215    if (ret != 0) {
216        return;
217    }
218    LE_RunLoop(LE_GetDefaultLoop());
219    LE_CloseLoop(LE_GetDefaultLoop());
220#endif
221    BEGET_LOGI("Cmd Client exit ");
222}
223