Skip to content

Commit 5482eb7

Browse files
committed
Fix man(1) interference issue
When ai-cli-lib installed and configured the man(1) command would respond with "no manual page for". * Use _git bisect_ to find that the commits for issue #1 were responsible for the problem. * Run _strace_ with the failing and succeeding version to find that the failing version did not issue the followin call: ``` 2484314 openat(3, "/usr/share/man/man1/true.1.gz", O_RDONLY) = 4 ``` * Run _ltrace_ to find that the failing version would issue ``` 2484586 strcspn("\\\376\\\177.1*", "?*{}\\") = 0 ``` where the succeeding issued ``` 2484651 strcspn("true.1*", "?*{}\\") = 6 ``` * Download _man(1)_ source code from https://gitlab.com/man-db/man-db to find the location of the call in `src/globbing.c:match_wildcard_in_directory`. * Theorize that common globally visible functions of _ai-cli-lib_ were interferring with _man(1)_ * Find global definitions that are not referenced and could therefore be defined as `static`. ``` cc -shared -fPIC -DINI_MAX_LINE=2048 -DINI_USE_STACK=0 -DINI_ALLOW_REALLOC=1 -DINI_STOP_ON_FIRST_ERROR=1 -Wall -Werror -L/usr/local/lib -O2 '-DDLL_EXTENSION="so"' ai_cli.c config.c ini.c fetch_anthropic.c fetch_hal.c fetch_openai.c fetch_llamacpp.c support.c -c nm *.o | sed 's/^ /x/' | awk '$2 ~ /[A-Z]/ && $2 != "U" {print $3}' | sort >defs nm *.o | sed 's/^ /x/' | awk '$2 == "U" {print $3}' | sort >refs comm -23 defs refs ``` * Improve handling of global definitions required only for testing through the new definition of the `UNIT_TEST` macro. * Remove unused functions. (The `set_program_name` function clashed with a same named one called by _man(1)_ and was probably the culprit.) * Further strengthen isolation by prefixing all publicly visible functions with `acl_`.
1 parent 179c232 commit 5482eb7

17 files changed

+285
-249
lines changed

src/Makefile

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,23 @@
1+
#
2+
# ai_cli - readline wrapper to obtain a generative AI suggestion
3+
#
4+
# GNU Makefile for building, installing, and testing ai-cli-lib
5+
#
6+
# Copyright 2023-2024 Diomidis Spinellis
7+
#
8+
# Licensed under the Apache License, Version 2.0 (the "License");
9+
# you may not use this file except in compliance with the License.
10+
# You may obtain a copy of the License at
11+
#
12+
# http://www.apache.org/licenses/LICENSE-2.0
13+
#
14+
# Unless required by applicable law or agreed to in writing, software
15+
# distributed under the License is distributed on an "AS IS" BASIS,
16+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17+
# See the License for the specific language governing permissions and
18+
# limitations under the License.
19+
#
20+
121
# Help: Set PREFIX=~ for a local install; PREFIX=/usr for a system install.
222
# Help: By default PREFIX is set to install in /usr/local.
323
PREFIX ?= /usr/local
@@ -69,7 +89,7 @@ e2e-test: $(PROGS) # Help: Test the readline hook
6989
grep Dave
7090

7191
all-tests: $(TEST_SRC) $(RL_SRC)
72-
$(CC) -DUNIT_TEST $(CFLAGS) $(LDFLAGS) all_tests.c $(TEST_SRC) $(RL_SRC) CuTest.c $(LIB) -ldl -lreadline -o $@
92+
$(CC) -DUNIT_TEST $(CFLAGS) $(LDFLAGS) all_tests.c -DUNIT_TEST $(TEST_SRC) $(RL_SRC) CuTest.c $(LIB) -ldl -lreadline -o $@
7393

7494
unit-test: all-tests # Help: Run unit tests
7595
./all-tests

src/ai_cli.c

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
*
33
* ai_cli - readline wrapper to obtain a generative AI suggestion
44
*
5-
* Copyright 2023 Diomidis Spinellis
5+
* Copyright 2023-2024 Diomidis Spinellis
66
*
77
* Licensed under the Apache License, Version 2.0 (the "License");
88
* you may not use this file except in compliance with the License.
@@ -49,9 +49,9 @@ static int *history_length_ptr;
4949
// Loaded configuration
5050
static config_t config;
5151

52-
// API fetch function, e.g. fetch_openai or fetch_llamacpp
52+
// API fetch function, e.g. acl_fetch_openai or acl_fetch_llamacpp
5353

54-
char * (*fetch)(config_t *config, const char *prompt, int history_length);
54+
static char * (*fetch)(config_t *config, const char *prompt, int history_length);
5555

5656
/*
5757
* Add the specified prompt to the RL history, as a comment if the
@@ -69,7 +69,7 @@ add_prompt_to_history(const char *prompt)
6969
}
7070

7171
char *commented_prompt;
72-
safe_asprintf(&commented_prompt, "%s %s", config.prompt_comment,
72+
acl_safe_asprintf(&commented_prompt, "%s %s", config.prompt_comment,
7373
prompt);
7474
add_history(commented_prompt);
7575
free(commented_prompt);
@@ -145,7 +145,7 @@ setup(void)
145145
vi_movement_keymap_ptr = dlsym(RTLD_DEFAULT, "vi_movement_keymap");
146146
history_length_ptr = dlsym(RTLD_DEFAULT, "history_length");
147147

148-
read_config(&config);
148+
acl_read_config(&config);
149149

150150
if (!config.prompt_system) {
151151
fprintf(stderr, "No default ai-cli configuration loaded. Installation problem?\n");
@@ -163,18 +163,18 @@ setup(void)
163163
REQUIRE(general, api);
164164

165165
if (strcmp(config.general_api, "openai") == 0) {
166-
fetch = fetch_openai;
166+
fetch = acl_fetch_openai;
167167
REQUIRE(openai, key);
168168
REQUIRE(openai, endpoint);
169169
} else if (strcmp(config.general_api, "anthropic") == 0) {
170-
fetch = fetch_anthropic;
170+
fetch = acl_fetch_anthropic;
171171
REQUIRE(anthropic, key);
172172
REQUIRE(anthropic, endpoint);
173173
REQUIRE(anthropic, version);
174174
} else if (strcmp(config.general_api, "hal") == 0) {
175-
fetch = fetch_hal;
175+
fetch = acl_fetch_hal;
176176
} else if (strcmp(config.general_api, "llamacpp") == 0) {
177-
fetch = fetch_llamacpp;
177+
fetch = acl_fetch_llamacpp;
178178
REQUIRE(llamacpp, endpoint);
179179
} else {
180180
fprintf(stderr, "Unsupported API: [%s].\n", config.general_api);

src/config.c

Lines changed: 49 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* ai-cli - readline wrapper to obtain a generative AI suggestion
44
* Configuration parsing and access
55
*
6-
* Copyright 2023 Diomidis Spinellis
6+
* Copyright 2023-2024 Diomidis Spinellis
77
*
88
* Licensed under the Apache License, Version 2.0 (the "License");
99
* you may not use this file except in compliance with the License.
@@ -30,7 +30,7 @@
3030
#include "ini.h"
3131

3232

33-
const char hidden_config_name[] = ".aicliconfig";
33+
static const char hidden_config_name[] = ".aicliconfig";
3434

3535
/*
3636
* Prefixes for providing n-shot user and assistant prompts in ini files.
@@ -39,19 +39,19 @@ const char hidden_config_name[] = ".aicliconfig";
3939
* user-1 = Disable breakpoint number 4
4040
* assistant-1 = delete 4
4141
*/
42-
const char prompt_ini_prefix[] = "prompt-";
43-
const char user_ini_prefix[] = "user-";
44-
const char assistant_ini_prefix[] = "assistant-";
42+
static const char prompt_ini_prefix[] = "prompt-";
43+
static const char user_ini_prefix[] = "user-";
44+
static const char assistant_ini_prefix[] = "assistant-";
4545

4646
/*
4747
* Prefixes for providing n-shot user and assistant prompts in
4848
* environment variables. Examples:
4949
* AI_CLI_prompt_gdb_user_1=Disable breakpoint number 4
5050
* AI_CLI_prompt_gdb_assistant_1=delete 4
5151
*/
52-
const char env_prompt_prefix[] = "AI_CLI_prompt_";
53-
const char user_env_prefix[] = "user_";
54-
const char assistant_env_prefix[] = "assistant_";
52+
static const char env_prompt_prefix[] = "AI_CLI_prompt_";
53+
static const char user_env_prefix[] = "user_";
54+
static const char assistant_env_prefix[] = "assistant_";
5555

5656
// Return true if the specified string starts with the given prefix
5757
STATIC bool
@@ -74,7 +74,7 @@ prompt_id(const char *entry)
7474
const char *id_end = strchr(id_begin, '_');
7575
if (!id_end)
7676
return NULL;
77-
return range_strdup(id_begin, id_end);
77+
return acl_range_strdup(id_begin, id_end);
7878
}
7979

8080
/*
@@ -86,12 +86,12 @@ prompt_id(const char *entry)
8686
STATIC int
8787
prompt_number(const char *name, const char *prompt_prefix)
8888
{
89-
int result = strtocard(name + strlen(prompt_prefix));
89+
int result = acl_strtocard(name + strlen(prompt_prefix));
9090
return result <= 0 || result > NPROMPTS ? -1 : result - 1;
9191
}
9292

9393
// Return true if the string contains the value true
94-
bool
94+
static bool
9595
strtobool(const char *string)
9696
{
9797
return strcmp(string, "true") == 0;
@@ -138,25 +138,25 @@ fixed_matcher(config_t *pconfig, const char* section,
138138
} while(0)
139139

140140
// In section, key alphabetic order
141-
MATCH(anthropic, endpoint, safe_strdup);
142-
MATCH(anthropic, key, safe_strdup);
141+
MATCH(anthropic, endpoint, acl_safe_strdup);
142+
MATCH(anthropic, key, acl_safe_strdup);
143143
MATCH(anthropic, max_tokens, atoi);
144-
MATCH(anthropic, model, safe_strdup);
144+
MATCH(anthropic, model, acl_safe_strdup);
145145
MATCH(anthropic, temperature, atof);
146146
MATCH(anthropic, top_k, atoi);
147147
MATCH(anthropic, top_p, atof);
148-
MATCH(anthropic, version, safe_strdup);
148+
MATCH(anthropic, version, acl_safe_strdup);
149149

150-
MATCH(binding, emacs, safe_strdup);
151-
MATCH(binding, vi, safe_strdup);
150+
MATCH(binding, emacs, acl_safe_strdup);
151+
MATCH(binding, vi, acl_safe_strdup);
152152

153-
MATCH(general, api, safe_strdup);
154-
MATCH(general, logfile, safe_strdup);
155-
MATCH(general, response_prefix, safe_strdup);
153+
MATCH(general, api, acl_safe_strdup);
154+
MATCH(general, logfile, acl_safe_strdup);
155+
MATCH(general, response_prefix, acl_safe_strdup);
156156
MATCH(general, timestamp, strtobool);
157157
MATCH(general, verbose, strtobool);
158158

159-
MATCH(llamacpp, endpoint, safe_strdup);
159+
MATCH(llamacpp, endpoint, acl_safe_strdup);
160160
MATCH(llamacpp, frequency_penalty, atof);
161161
MATCH(llamacpp, mirostat, atoi);
162162
MATCH(llamacpp, mirostat_eta, atof);
@@ -174,13 +174,13 @@ fixed_matcher(config_t *pconfig, const char* section,
174174
MATCH(llamacpp, top_p, atof);
175175
MATCH(llamacpp, typical_p, atof);
176176

177-
MATCH(openai, endpoint, safe_strdup);
178-
MATCH(openai, key, safe_strdup);
179-
MATCH(openai, model, safe_strdup);
177+
MATCH(openai, endpoint, acl_safe_strdup);
178+
MATCH(openai, key, acl_safe_strdup);
179+
MATCH(openai, model, acl_safe_strdup);
180180
MATCH(openai, temperature, atof);
181181

182-
MATCH(prompt, context, strtocard);
183-
MATCH(prompt, system, safe_strdup);
182+
MATCH(prompt, context, acl_strtocard);
183+
MATCH(prompt, system, acl_safe_strdup);
184184

185185
return 0;
186186
}
@@ -208,9 +208,9 @@ fixed_program_matcher(config_t *pconfig, const char* name, const char* value)
208208
return 1; \
209209
} \
210210
} while (0)
211-
MATCH_PROGRAM(comment, safe_strdup);
212-
MATCH_PROGRAM(context, strtocard);
213-
MATCH_PROGRAM(system, safe_strdup);
211+
MATCH_PROGRAM(comment, acl_safe_strdup);
212+
MATCH_PROGRAM(context, acl_strtocard);
213+
MATCH_PROGRAM(system, acl_safe_strdup);
214214

215215
return 0;
216216
}
@@ -233,7 +233,7 @@ config_handler(void* user, const char* section, const char* name,
233233
return 1;
234234

235235
if (!starts_with(section, prompt_ini_prefix))
236-
errorf("Unknown configuration section [%s], name `%s'.", section, name);
236+
acl_errorf("Unknown configuration section [%s], name `%s'.", section, name);
237237

238238
/*
239239
* A program specific section. It can provide user or assistant
@@ -260,17 +260,17 @@ config_handler(void* user, const char* section, const char* name,
260260
if (starts_with(name, user_ini_prefix)) {
261261
int n = prompt_number(name, user_ini_prefix);
262262
if (n == -1)
263-
errorf("Invalid prompt number, section [%s], name `%s', value `%s'.", section, name, value);
263+
acl_errorf("Invalid prompt number, section [%s], name `%s', value `%s'.", section, name, value);
264264
pconfig->prompt_user[n] = strdup(value);
265265
return 1;
266266
} else if (starts_with(name, assistant_ini_prefix)) {
267267
int n = prompt_number(name, assistant_ini_prefix);
268268
if (n == -1)
269-
errorf("Invalid prompt number, section [%s], name `%s', value `%s'.", section, name, value);
269+
acl_errorf("Invalid prompt number, section [%s], name `%s', value `%s'.", section, name, value);
270270
pconfig->prompt_assistant[n] = strdup(value);
271271
return 1;
272272
}
273-
errorf("Unknown configuration section [%s], name `%s'.", section, name);
273+
acl_errorf("Unknown configuration section [%s], name `%s'.", section, name);
274274
return 0; /* unknown section/name, error */
275275
}
276276

@@ -307,7 +307,7 @@ env_override(config_t *config)
307307
// E.g. sqlite3 or gitconfig (which will be named git-config)
308308
char *program_name = prompt_id(entry);
309309
if (!program_name)
310-
errorf("Missing program identifier in prompt environment variable %s", entry);
310+
acl_errorf("Missing program identifier in prompt environment variable %s", entry);
311311

312312
// Skip matching of programs other than ours
313313
if (strcmp(program_name, config->program_name) != 0) {
@@ -319,24 +319,24 @@ env_override(config_t *config)
319319
strlen(program_name);
320320
const char *prompt_name_end = strchr(prompt_name_begin, '=');
321321
if (!prompt_name_end)
322-
errorf("Missing value in prompt environment variable %s", entry);
323-
char *prompt_name = range_strdup(prompt_name_begin, prompt_name_end);
322+
acl_errorf("Missing value in prompt environment variable %s", entry);
323+
char *prompt_name = acl_range_strdup(prompt_name_begin, prompt_name_end);
324324
const char *prompt_value = prompt_name_end + 1;
325325

326326
if (starts_with(prompt_name, user_env_prefix)) {
327327
int n = prompt_number(prompt_name, user_env_prefix);
328328
if (n == -1)
329-
errorf("Invalid prompt value in environment variable %s", entry);
329+
acl_errorf("Invalid prompt value in environment variable %s", entry);
330330
else
331331
config->prompt_user[n] = strdup(prompt_value);
332332
} else if (starts_with(prompt_name, assistant_env_prefix)) {
333333
int n = prompt_number(prompt_name, assistant_env_prefix);
334334
if (n == -1)
335-
errorf("Invalid prompt value in environment variable %s", entry);
335+
acl_errorf("Invalid prompt value in environment variable %s", entry);
336336
else
337337
config->prompt_assistant[n] = strdup(prompt_value);
338338
} else if (!fixed_program_matcher(config, prompt_name, prompt_value))
339-
errorf("Invalid name in environment variable %s", entry);
339+
acl_errorf("Invalid name in environment variable %s", entry);
340340
free(program_name);
341341
free(prompt_name);
342342
}
@@ -350,16 +350,16 @@ ini_checked_parse(const char* filename, ini_handler handler, config_t *config)
350350
int val = ini_parse(filename, handler, config);
351351
// When unable to open file val is -1, which we ignore
352352
if (val > 0)
353-
errorf("%s:%d:1: Initialization file error", filename, val);
353+
acl_errorf("%s:%d:1: Initialization file error", filename, val);
354354
}
355355

356356
/*
357357
* Read the configuration file from diverse directories into config.
358358
*/
359359
void
360-
read_config(config_t *config)
360+
acl_read_config(config_t *config)
361361
{
362-
config->program_name = short_program_name();
362+
config->program_name = acl_short_program_name();
363363

364364
ini_checked_parse("/usr/share/ai-cli/config", config_handler, config);
365365
ini_checked_parse("/usr/local/share/ai-cli/config", config_handler, config);
@@ -370,11 +370,11 @@ read_config(config_t *config)
370370
if ((home_dir = getenv("HOME")) != NULL) {
371371
char *home_config;
372372

373-
safe_asprintf(&home_config, "%s/%s", home_dir, "share/ai-cli/config");
373+
acl_safe_asprintf(&home_config, "%s/%s", home_dir, "share/ai-cli/config");
374374
ini_checked_parse(home_config, config_handler, config);
375375
free(home_config);
376376

377-
safe_asprintf(&home_config, "%s/%s", home_dir, hidden_config_name);
377+
acl_safe_asprintf(&home_config, "%s/%s", home_dir, hidden_config_name);
378378
ini_checked_parse(home_config, config_handler, config);
379379
free(home_config);
380380
}
@@ -384,6 +384,7 @@ read_config(config_t *config)
384384
env_override(config);
385385
}
386386

387+
#if defined(UNIT_TEST)
387388
/*
388389
* Read the configuration file from the specified file path into config.
389390
* This allows testing the config handler.
@@ -393,29 +394,20 @@ read_file_config(config_t *config, const char *file_path)
393394
{
394395
// Allow unit tests to override this value
395396
if (!config->program_name)
396-
config->program_name = short_program_name();
397+
config->program_name = acl_short_program_name();
397398

398399
ini_checked_parse(file_path, config_handler, config);
399400
env_override(config);
400401
}
402+
#endif
401403

402404
/*
403405
* Return the system role prompt string in dynamically allocated memory.
404406
*/
405407
char *
406-
system_role_get(config_t *config)
408+
acl_system_role_get(config_t *config)
407409
{
408410
char *system_role;
409-
safe_asprintf(&system_role, config->prompt_system, config->program_name);
411+
acl_safe_asprintf(&system_role, config->prompt_system, config->program_name);
410412
return system_role;
411413
}
412-
413-
/*
414-
* Set the name of the program executing the library.
415-
* Provided for testing.
416-
*/
417-
void
418-
set_program_name(config_t *config, const char *name)
419-
{
420-
config->program_name = name;
421-
}

0 commit comments

Comments
 (0)