CFE-2986: Added getdir command to cf-net, copies directory and files#6049
CFE-2986: Added getdir command to cf-net, copies directory and files#6049SimonThalvorsen wants to merge 1 commit intocfengine:masterfrom
Conversation
f7cebad to
40a6c09
Compare
larsewi
left a comment
There was a problem hiding this comment.
It would be nicer if we extended cf-net get to get both files and directories, instead of having two separate sub-commands. Please investigate how rsync does this and try to do it in a similar way here.
@larsewi on the other hand, cf-net was written mainly as a protocol testing tool, and the commands mirror their protocol command names. If the There is also some advantage to not being too dynamic with this stuff. If you make a CLI that forces the user to specify if they are copying a file or a folder, you can prevent some annoying / confusing situations. We could for example make a new subcommand, |
40a6c09 to
2bd2729
Compare
larsewi
left a comment
There was a problem hiding this comment.
Please break this up a little. Maybe a recursive solution would be more elegant given that it is a recursive problem.
cf-net/cf-net.c
Outdated
| filedata->print_stats = opts->print_stats; | ||
|
|
||
| filedata->ret = ProtocolStatGet(conn, filedata->remote_file, | ||
| filedata->local_file, 0644, filedata->print_stats); |
There was a problem hiding this comment.
Maybe you should use the remote file permissions (available from the ProtocolStat) instead?
cf-net/cf-net.c
Outdated
| char curr_dir[PATH_MAX]; | ||
| snprintf(curr_dir, sizeof(curr_dir), "%s/%s", local_dir, curr); | ||
|
|
||
| GetFileData *filedata = calloc(1, sizeof(GetFileData)); |
There was a problem hiding this comment.
This does not need to be heap allocated
cf-net/cf-net.c
Outdated
| continue; | ||
| } | ||
|
|
||
| snprintf(filedata->local_file, PATH_MAX, "%s", curr_dir); |
There was a problem hiding this comment.
Please check for truncation on all the calls to snprintf
cf-net/cf-net.c
Outdated
| return invalid_command("getdir"); | ||
| break; |
There was a problem hiding this comment.
Unreachable break after return
cf-net/cf-net.c
Outdated
| if (local_dir != NULL) | ||
| { | ||
| Log(LOG_LEVEL_INFO, | ||
| "Warning: multiple occurences of -o in command, "\ |
There was a problem hiding this comment.
| "Warning: multiple occurences of -o in command, "\ | |
| "Warning: multiple occurrences of -o in command, "\ |
2bd2729 to
e9f4cb3
Compare
e9f4cb3 to
5b55d16
Compare
larsewi
left a comment
There was a problem hiding this comment.
There should be a recursion depth limit to prevent a potential malicious or misconfigured server with symlink loops (or deeply nested dirs) to cause stack overflow.
We should probably stick to a maximum path size for the same reasons above.
cf-net/cf-net.c
Outdated
| char local_full[PATH_MAX]; | ||
| written = snprintf(local_full, sizeof(local_full), "%s/%s", local_path, item); | ||
| if (written < 0 || written >= sizeof(local_full)) { | ||
| Log(LOG_LEVEL_ERR, "Path too long for full local path: %s",local_full); |
There was a problem hiding this comment.
Here you print the truncated path and not the intended full path. This could be a bit misleading.
| if (written < 0 || written >= len) { | ||
| Log(LOG_LEVEL_ERR, "Path too long for local path: %s", temp); | ||
| free(local_dir); | ||
| return -1; |
cf-net/cf-net.c
Outdated
| free(local_dir); | ||
| return -1; | ||
| } | ||
| int written = snprintf(temp, len, "%s/%s", local_dir, basename(remote_dir)); |
There was a problem hiding this comment.
Consider using StringFormat()
cf-net/cf-net.c
Outdated
| char *remote_dir = args[0]; | ||
| if (specified_path) | ||
| { | ||
| size_t len = strlen(local_dir) + strlen(basename(remote_dir)) + 2; // / and '\0' |
There was a problem hiding this comment.
basename() may modify it's argument
cf-net/cf-net.c
Outdated
| } | ||
|
|
||
| // Helper: Recursively process directory entries | ||
| static void process_dir_recursive(AgentConnection *conn, |
There was a problem hiding this comment.
The function returns void. If all files fail to download, CFNetGetDir still returns 0 (success).
There was a problem hiding this comment.
I do not see why this is an issue, failing to stat/get/mkdir will all log their respective errors. So you would rather it return -1 on any one error instead of continue?
There was a problem hiding this comment.
If you were to use it in a script, then it would be easier to check the exit value rather then parsing log messages. Does the other commands exit with 0 on failure?
| } | ||
|
|
||
| // Helper: Get a single file with permissions | ||
| static bool CFNetGetWithPerms(AgentConnection *conn, const char *remote_path, |
There was a problem hiding this comment.
File permissions are preserved via ProtocolGet, but created directories don't get the remote directory's permissions applied.
cf-net/cf-net.c
Outdated
| SeqDestroy(items); | ||
| } | ||
|
|
||
| static int CFNetGetDir( CFNetOptions *opts, const char *hostname, char **args) |
There was a problem hiding this comment.
| static int CFNetGetDir( CFNetOptions *opts, const char *hostname, char **args) | |
| static int CFNetGetDir(CFNetOptions *opts, const char *hostname, char **args) |
cf-net/cf-net.c
Outdated
| return -1; | ||
| } | ||
| int written = snprintf(temp, len, "%s/%s", local_dir, basename(remote_dir)); | ||
| if (written < 0 || written >= len) { |
There was a problem hiding this comment.
| if (written < 0 || written >= len) { | |
| if (written < 0 || written >= len) | |
| { |
cf-net/cf-net.c
Outdated
| assert(conn != NULL && remote_path != NULL && local_path != NULL); | ||
|
|
||
| struct stat perms; | ||
| if (!ProtocolStat(conn, remote_path, &perms)) { |
There was a problem hiding this comment.
Try to be consistent with the braces. CONTRIBUTING.md says to put them on a new line. Look through the rest of the code, there are more cases like this.
| if (!ProtocolStat(conn, remote_path, &perms)) { | |
| if (!ProtocolStat(conn, remote_path, &perms)) | |
| { |
cf-net/cf-net.c
Outdated
|
|
||
| if (has_output_path) { | ||
| written = snprintf(path, sizeof(path), "%s/%s/", local_base, subdir); | ||
| } else { |
There was a problem hiding this comment.
Use Allman style, not K&R style (} else {)
d68100f to
047b068
Compare
larsewi
left a comment
There was a problem hiding this comment.
Make sure to check return values of functions. It's OK to do best effort. I.e., we encounter an error but continue if it's safe. But in this case we need to cache the error so that we can later exit with failure. For some errors, like if we fail to create a directory, it does not make sense to try to fetch the files that should be in that directory afterwards.
cf-net/cf-net.c
Outdated
| static bool CFNetGetWithPerms(AgentConnection *conn, const char *remote_path, | ||
| const char *local_path, bool print_stats) | ||
| { | ||
| assert(conn != NULL && remote_path != NULL && local_path != NULL); |
There was a problem hiding this comment.
Try this:
| assert(conn != NULL && remote_path != NULL && local_path != NULL); | |
| assert(conn != NULL); | |
| assert(remote_path != NULL); | |
| assert(local_path != NULL); |
Maybe it will silence the CodeQL warning. It's also OK to simply dismiss it. However, by fixing it, the next time someone touches this code, the warning does not pop back up. I'm fine with either way.
cf-net/cf-net.c
Outdated
| bool* created = NULL; | ||
| bool force = false; | ||
| MakeParentDirectoryPerms(path, force, created, perms); |
There was a problem hiding this comment.
Personally I would just pass NULL here since you don't care if the directory was actually created or not. I get the idea that you want to improve readability with having "named" arguments. But in this case, it's more confusing than helpful.
| bool* created = NULL; | |
| bool force = false; | |
| MakeParentDirectoryPerms(path, force, created, perms); | |
| const bool force = false; | |
| MakeParentDirectoryPerms(path, force, NULL, perms); |
The intended use of this argument is:
const bool force = false;
bool created;
MakeParentDirectoryPerms(path, force, &created, perms);
if (created)
{
// The directory was created
}
else
{
// The directory already existed
}Where, created is set to true by the function itself if the directory was created.
The MakeParentDirectoryPerms function also returns an error. Maybe you should check this error message, and not try to fetch the files that should be inside the directory if you failed to create it.
cf-net/cf-net.c
Outdated
| static int process_dir_recursive(AgentConnection *conn, | ||
| const char *remote_path, | ||
| const char *local_path, | ||
| bool has_output_path, | ||
| bool print_stats, | ||
| int limit) |
There was a problem hiding this comment.
Inconsistent indents
| static int process_dir_recursive(AgentConnection *conn, | |
| const char *remote_path, | |
| const char *local_path, | |
| bool has_output_path, | |
| bool print_stats, | |
| int limit) | |
| static int process_dir_recursive(AgentConnection *conn, | |
| const char *remote_path, | |
| const char *local_path, | |
| bool has_output_path, | |
| bool print_stats, | |
| int limit) |
|
|
||
| // Helper: Create local directory path | ||
| static bool create_local_dir(const char *local_base, const char *subdir, | ||
| bool has_output_path, mode_t perms) |
There was a problem hiding this comment.
It's not obvious what the has_output_path variable does. I think this function deserves a doxygen comment.
| } | ||
|
|
||
| int written; | ||
| Seq *items = ProtocolOpenDir(conn, remote_path); |
There was a problem hiding this comment.
Make sure to free this sequence in all the return paths
cf-net/cf-net.c
Outdated
| bool print_stats, | ||
| int limit) | ||
| { | ||
| if (0 > limit - 1) |
There was a problem hiding this comment.
This recursion limit test is confusing. Is not this the same as?
| if (0 > limit - 1) | |
| if (limit <= 0) |
cf-net/cf-net.c
Outdated
| case 'l': | ||
| { | ||
| char *lim; | ||
| long val = strtol(optarg, &lim, 10); |
There was a problem hiding this comment.
Need to check for overflow. We have functions that do this in libntech. I would suggest using those.
cf-net/cf-net.c
Outdated
| args = &(args[optind]); | ||
| argc -= optind; | ||
| char *remote_dir = args[0]; | ||
| char *base = basename(remote_dir); |
There was a problem hiding this comment.
basename may modify its argument
cf-net/cf-net.c
Outdated
| assert(hostname != NULL); | ||
| assert(args != NULL); | ||
| char *local_dir = NULL; | ||
| int limit = 10; |
There was a problem hiding this comment.
Default should probably be more than 10.
| int limit = 10; | |
| int limit = 32; |
cf-net/cf-net.c
Outdated
| written = snprintf(path, sizeof(path), "%s/%s/%s/", cwd, local_base, subdir); | ||
| } | ||
|
|
||
| if (written < 0 || written >= sizeof(path)) |
There was a problem hiding this comment.
Static checkers may complain that you are comparing singed and unsigned. Consider casting to unsigned (safe since the negative case is handled first)
| if (written < 0 || written >= sizeof(path)) | |
| if (written < 0 || (size_t) written >= sizeof(path)) |
047b068 to
62eb2df
Compare
Ticket: CFE-2986 Changelog: Title Signed-off-by: Simon Halvorsen <simon.halvorsen@northern.tech>
62eb2df to
c2b59c7
Compare
Ticket: CFE-2986
Changelog: Title