1 /* vi: set et sw=4 ts=4 cino=t0,(0: */
2 /* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
4 * This file is part of gum
6 * Copyright (C) 2013 Intel Corporation.
8 * Contact: Imran Zaman <imran.zaman@intel.com>
10 * This library is free software; you can redistribute it and/or
11 * modify it under the terms of the GNU Lesser General Public
12 * License as published by the Free Software Foundation; either
13 * version 2.1 of the License, or (at your option) any later version.
15 * This library is distributed in the hope that it will be useful, but
16 * WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 * Lesser General Public License for more details.
20 * You should have received a copy of the GNU Lesser General Public
21 * License along with this library; if not, write to the Free Software
22 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
29 #if defined HAVE_SYS_XATTR_H
30 #include <sys/xattr.h>
32 #include <linux/xattr.h>
37 #include <glib/gstdio.h>
39 #include "common/gum-file.h"
40 #include "common/gum-string-utils.h"
41 #include "common/gum-defines.h"
42 #include "common/gum-log.h"
43 #include "common/gum-error.h"
44 #include "common/gum-config.h"
48 * @short_description: Utility functions for file handling
50 * @include: gum/common/gum-file.h
52 * Below is the code snippet, which demonstrate how can file update function be
53 * used to add, remove or modify an entry in the user/group database file
58 * gboolean _custom_update_file_entries (GObject *obj, GumOpType op,
59 * FILE *source_file, FILE *dup_file, gpointer user_data, GError **error)
61 * //loop through the file entries and modify as per operation type
65 * void update_file_entries ()
67 * GObject* obj = NULL;
68 * GError *error = NULL;
69 * const gchar *source_file = "/tmp/passwd";
70 * gum_file_update (obj, GUM_OPTYPE_ADD,
71 * (GumFileUpdateCB)_custom_update_file_entries, source_file, NULL,
81 * Data structure that contains information about file stream as defined in
87 * @GUM_OPTYPE_ADD: add an entry
88 * @GUM_OPTYPE_DELETE: delete an entry
89 * @GUM_OPTYPE_MODIFY: modify an entry
91 * This enumeration lists the operations on file entry.
96 * @object: (transfer none): the instance of #GObject
97 * @op: (transfer none): the #GumOpType operation to be done on the file entry
98 * @source_file: (transfer none): the source file pointer
99 * @dup_file: (transfer none): the duplicate file pointer
100 * @user_data: the user data
101 * @error: (transfer none): the #GError which is set in case of an error
103 * Callback can be used for adding, deleting or modifying file entries. It is
104 * invoked in #gum_file_update function.
106 * Returns: TRUE if successful, FALSE otherwise and @error is set.
109 #define GUM_PERM 0777
116 #if defined(HAVE_LSETXATTR)
117 GumConfig *config = NULL;
120 config = gum_config_new (NULL);
121 const gchar *smack_label = gum_config_get_string (config, key);
123 len = strlen (smack_label);
124 DBG ("Set smack label %s for path %s with len %d",smack_label, path,
128 * Set smack64 extended attribute (when provided in the config file)
132 setxattr (path, XATTR_NAME_SMACK, smack_label, len, 0) != 0) {
133 g_object_unref (config);
136 g_object_unref (config);
147 if (!fn || !(fp = fopen (fn, mode))) {
149 WARN ("Could not open file '%s', error: %s", fn, strerror(errno));
156 _copy_file_attributes (
157 const gchar *from_path,
158 const gchar *to_path)
161 ssize_t attrs_size = 0;
162 struct stat from_stat;
164 if (stat (from_path, &from_stat) < 0 ||
165 chmod (to_path, from_stat.st_mode) < 0 ||
166 chown (to_path, from_stat.st_uid, from_stat.st_gid) < 0) {
170 /* copy extended attributes */
171 #if defined(HAVE_LLISTXATTR) && \
172 defined(HAVE_LGETXATTR) && \
173 defined(HAVE_LSETXATTR)
174 attrs_size = llistxattr (from_path, NULL, 0);
175 if (attrs_size > 0) {
177 gchar *names = g_new0 (gchar, attrs_size + 1);
178 if (names && llistxattr (from_path, names, attrs_size) > 0) {
180 gchar *name = names, *value = NULL;
181 gchar *end_names = names + attrs_size;
182 names[attrs_size] = '\0';
185 while (name != end_names) {
187 name = strchr (name,'\0')+1;
188 if (name && name[0] != '\0') {
189 size = lgetxattr (from_path, name, NULL, 0);
191 (value = g_realloc (value, size)) &&
192 lgetxattr (from_path, name, value, size) > 0) {
194 if (lsetxattr (to_path, name, value, size, 0) != 0) {
211 * gum_file_open_db_files:
212 * @source_file_path: (transfer none): the path to source file
213 * @dup_file_path: (transfer none): the path to duplicate file, created from
215 * @source_file: (transfer none): the file pointer created when source file
216 * is opened in read mode
217 * @dup_file: (transfer none): the file pointer created when duplicate file is
218 * opened in write mode
219 * @error: (transfer none): the #GError which is set in case of an error
221 * Opens the source file @source_file_path in read mode. Then creates the
222 * duplicate file @dup_file_path in write mode and copies source file attributes
223 * to the duplicate file. Open file handles are set in @source_file and
226 * Returns: TRUE if successful, FALSE otherwise and @error is set.
229 gum_file_open_db_files (
230 const gchar *source_file_path,
231 const gchar *dup_file_path,
238 gboolean retval = TRUE;
240 if (!source_file || !dup_file || !source_file_path || !dup_file_path) {
241 GUM_RETURN_WITH_ERROR(GUM_ERROR_FILE_OPEN, "Invalid arguments",
245 if (!(source = _open_file (source_file_path, "r"))) {
246 GUM_RETURN_WITH_ERROR (GUM_ERROR_FILE_OPEN, "Unable to open orig file",
250 if (!(dup = _open_file (dup_file_path, "w+"))) {
251 GUM_SET_ERROR (GUM_ERROR_FILE_OPEN, "Unable to open new file",
252 error, retval, FALSE);
256 if (!_copy_file_attributes (source_file_path, dup_file_path)) {
257 GUM_SET_ERROR (GUM_ERROR_FILE_ATTRIBUTE,
258 "Unable to get/set file attributes", error, retval, FALSE);
262 if (!_set_smack64_attr (dup_file_path,
263 GUM_CONFIG_GENERAL_SMACK64_NEW_FILES)) {
264 GUM_SET_ERROR (GUM_ERROR_FILE_ATTRIBUTE,
265 "Unable to set smack file attributes", error, retval, FALSE);
269 *source_file = source;
275 if (source) fclose(source);
278 g_unlink(dup_file_path);
287 * gum_file_close_db_files:
288 * @source_file_path: (transfer none): the path to source file
289 * @dup_file_path: (transfer none): the path to duplicate file
290 * @source_file: (transfer none): the source file pointer
291 * @dup_file: (transfer none): the duplicate file pointer
292 * @error: (transfer none): the #GError which is set in case of an error
294 * Closes the duplicate file @dup_file_path after flushing all the data. Backup
295 * of the source file @source_file_path is created and duplicate file is renamed
296 * to as the source file.
298 * Returns: TRUE if successful, FALSE otherwise and @error is set.
301 gum_file_close_db_files (
302 const gchar *source_file_path,
303 const gchar *dup_file_path,
308 gboolean retval = TRUE;
309 gchar *old_file_path = NULL;
311 if (source_file) fclose (source_file);
314 fflush (dup_file) != 0 ||
315 fsync (fileno (dup_file)) != 0 ||
316 fclose (dup_file) != 0) {
317 GUM_SET_ERROR (GUM_ERROR_FILE_WRITE, "File write failure", error,
322 if (!source_file_path) {
323 GUM_SET_ERROR(GUM_ERROR_FILE_WRITE, "null source file path", error,
328 if (!dup_file_path) {
329 GUM_RETURN_WITH_ERROR(GUM_ERROR_FILE_WRITE, "null dest file path",
333 if ((old_file_path = g_strdup_printf ("%s.old", source_file_path))) {
334 /* delete obsolote backup file if any */
335 g_unlink (old_file_path);
336 /* Move source file to old file and dup file as updated file */
337 if (link (source_file_path, old_file_path) != 0) {
338 WARN("Could not create a backup file for '%s'", source_file_path);
341 WARN("Could not create a backup file for '%s'", source_file_path);
344 if (g_rename (dup_file_path, source_file_path) != 0) {
345 GUM_SET_ERROR (GUM_ERROR_FILE_MOVE, "Unable to move file", error,
347 g_unlink(old_file_path);
350 g_free (old_file_path);
353 if (dup_file_path) g_unlink (dup_file_path);
360 * @object: (transfer none): the instance of #GObject; can be NULL
361 * @op: (transfer none): the #GumOpType operation to be done on file entry
363 * @callback: (transfer none): the callback #GumFileUpdateCB to be invoked
364 * when the source and duplicate files are opened to be handled
365 * @source_file_path: (transfer none): the source file path
366 * @user_data: user data to be passed on to the @callback
367 * @error: (transfer none): the #GError which is set in case of an error
369 * Opens the files and invokes the callback to do the required operation.
370 * Finally files are flushed and closed.
372 * Returns: TRUE if successful, FALSE otherwise and @error is set.
378 GumFileUpdateCB callback,
379 const gchar *source_file_path,
383 gboolean retval = TRUE;
384 FILE *source_file = NULL, *dup_file = NULL;
385 gchar *dup_file_path = NULL;
387 dup_file_path = g_strdup_printf ("%s-tmp.%lu", source_file_path,
388 (unsigned long)getpid ());
389 retval = gum_file_open_db_files (source_file_path, dup_file_path,
390 &source_file, &dup_file, error);
391 if (!retval) goto _finished;
393 /* Update, sync and close file */
395 GUM_SET_ERROR (GUM_ERROR_FILE_WRITE,
396 "File write function not specified", error, retval, FALSE);
400 retval = (*callback) (object, op, source_file, dup_file, user_data, error);
405 retval = gum_file_close_db_files (source_file_path, dup_file_path,
406 source_file, dup_file, error);
412 if (dup_file) fclose (dup_file);
413 g_unlink (dup_file_path);
414 if (source_file) fclose (source_file);
417 g_free (dup_file_path);
424 * @username: (transfer none): name of the user
425 * @filename: (transfer none): path to the file
427 * Gets the passwd structure from the file based on username.
429 * Returns: (transfer full): passwd structure if successful, NULL otherwise.
433 const gchar *username,
434 const gchar *filename)
436 struct passwd *pent = NULL;
439 if (!username || !filename) {
443 if (!(fp = _open_file (filename, "r"))) {
446 while ((pent = fgetpwent (fp)) != NULL) {
447 if(g_strcmp0 (username, pent->pw_name) == 0)
459 * @filename: (transfer none): path to the file
461 * Gets the passwd structure from the file based on uid.
463 * Returns: (transfer full): passwd structure if successful, NULL otherwise.
468 const gchar *filename)
470 struct passwd *pent = NULL;
477 if (!(fp = _open_file (filename, "r"))) {
481 while ((pent = fgetpwent (fp)) != NULL) {
482 if(uid == pent->pw_uid)
492 * gum_file_find_user_by_gid:
493 * @primary_gid: primary gid of the user
494 * @filename: (transfer none): path to the file
496 * Gets the passwd structure from the file based on the primary group id.
498 * Returns: (transfer full): passwd structure if successful, NULL otherwise.
501 gum_file_find_user_by_gid (
503 const gchar *filename)
505 struct passwd *pent = NULL;
508 if (!filename || primary_gid == GUM_GROUP_INVALID_GID) {
512 if (!(fp = _open_file (filename, "r"))) {
516 while ((pent = fgetpwent (fp)) != NULL) {
517 if(primary_gid == pent->pw_gid)
528 * @username: (transfer none): name of the user
529 * @filename: (transfer none): path to the file
531 * Gets the spwd structure from the file based on the username.
533 * Returns: (transfer full): spwd structure if successful, NULL otherwise.
537 const gchar *username,
538 const gchar *filename)
540 struct spwd *spent = NULL;
543 if (!username || !filename) {
547 if (!(fp = _open_file (filename, "r"))) {
551 while ((spent = fgetspent (fp)) != NULL) {
552 if(g_strcmp0 (username, spent->sp_namp) == 0)
563 * @grname: (transfer none): name of the group
564 * @filename: (transfer none): path to the file
566 * Gets the group structure from the file based on the groupname @grname.
568 * Returns: (transfer full): group structure if successful, NULL otherwise.
573 const gchar *filename)
575 struct group *gent = NULL;
578 if (!grname || !filename) {
582 if (!(fp = _open_file (filename, "r"))) {
585 while ((gent = fgetgrent (fp)) != NULL) {
586 if(g_strcmp0 (grname, gent->gr_name) == 0)
597 * @gid: id of the group
598 * @filename: (transfer none): path to the file
600 * Gets the group structure from the file based on the gid.
602 * Returns: (transfer full): group structure if successful, NULL otherwise.
607 const gchar *filename)
609 struct group *gent = NULL;
616 if (!(fp = _open_file (filename, "r"))) {
619 while ((gent = fgetgrent (fp)) != NULL) {
620 if(gid == gent->gr_gid)
631 * @grname: (transfer none): name of the group
632 * @filename: (transfer none): path to the file
634 * Gets the sgrp structure from the file based on the groupname @grname.
636 * Returns: (transfer full): sgrp structure if successful, NULL otherwise.
641 const gchar *filename)
643 struct sgrp *sgent = NULL;
646 if (!grname || !filename) {
650 if (!g_file_test (filename, G_FILE_TEST_EXISTS) ||
651 !(fp = _open_file (filename, "r"))) {
655 while ((sgent = fgetsgent (fp)) != NULL) {
656 if(g_strcmp0 (grname, sgent->sg_namp) == 0)
667 * @dir: (transfer none): directory path
668 * @filename: (transfer none): name of the file
670 * Builds complete file path based on the @filename and @dir.
672 * Returns: (transfer full): the #GFile if successful, NULL otherwise.
677 const gchar *filename)
682 if (!dir || !filename) {
686 fn = g_build_filename (dir, filename, NULL);
688 file = g_file_new_for_path (fn);
696 _copy_dir_recursively (
704 gboolean retval = TRUE;
705 gboolean stop = FALSE;
706 const gchar *src_fname = NULL;
707 gchar *src_filepath = NULL, *dest_filepath = NULL;
708 GDir *src_dir = NULL;
709 struct stat stat_entry;
712 GUM_RETURN_WITH_ERROR (GUM_ERROR_HOME_DIR_COPY_FAILURE,
713 "Invalid directory path(s)", error, FALSE);
716 DBG ("copy directory %s -> %s", src, dest);
717 src_dir = g_dir_open (src, 0, NULL);
719 GUM_RETURN_WITH_ERROR (GUM_ERROR_HOME_DIR_COPY_FAILURE,
720 "Invalid source directory path", error, FALSE);
723 while ((src_fname = g_dir_read_name (src_dir))) {
725 GFile *src_file = NULL, *dest_file = NULL;
727 src_filepath = g_build_filename (src, src_fname, NULL);
728 stop = (lstat(src_filepath, &stat_entry) != 0);
729 if (stop) goto _free_data;
731 dest_filepath = g_build_filename (dest, src_fname, NULL);
732 src_file = g_file_new_for_path (src_filepath);
733 dest_file = g_file_new_for_path (dest_filepath);
735 if (S_ISDIR (stat_entry.st_mode)) {
736 DBG ("copy directory %s", src_filepath);
737 gint mode = GUM_PERM & ~umask;
738 g_mkdir_with_parents (dest_filepath, mode);
739 stop = !_set_smack64_attr (dest_filepath,
740 GUM_CONFIG_GENERAL_SMACK64_USER_FILES);
742 stop = !_copy_dir_recursively (src_filepath, dest_filepath, uid,
745 DBG ("copy file %s", src_filepath);
746 if (!g_file_copy (src_file, dest_file,
747 G_FILE_COPY_OVERWRITE | G_FILE_COPY_NOFOLLOW_SYMLINKS, NULL,
749 WARN("File copy failure error %d:%s", err ? err->code : 0,
750 err ? err->message : "");
751 if (err) g_error_free (err);
755 stop = !_set_smack64_attr (dest_filepath,
756 GUM_CONFIG_GENERAL_SMACK64_USER_FILES);
758 if (!stop) stop = (chown (dest_filepath, uid, gid) < 0);
761 g_free (src_filepath);
762 g_free (dest_filepath);
763 GUM_OBJECT_UNREF (src_file);
764 GUM_OBJECT_UNREF (dest_file);
766 GUM_SET_ERROR (GUM_ERROR_HOME_DIR_COPY_FAILURE,
767 "Home directory copy failure", error, retval, FALSE);
772 g_dir_close (src_dir);
777 * gum_file_create_home_dir:
778 * @home_dir: path to the user home directory
779 * @uid: id of the user
780 * @gid: group id of the user
781 * @umask: the umask to be used for setting the mode of the files/directories
782 * @error: (transfer none): the #GError which is set in case of an error
784 * Creates the home directory of the user. All the files from the
785 * #GUM_CONFIG_GENERAL_SKEL_DIR are copied (recursively) to the user home
788 * Returns: TRUE if successful, FALSE otherwise and @error is set.
791 gum_file_create_home_dir (
792 const gchar *home_dir,
798 gboolean retval = TRUE;
799 gint mode = GUM_PERM & ~umask;
802 GUM_RETURN_WITH_ERROR (GUM_ERROR_HOME_DIR_CREATE_FAILURE,
803 "Invalid home directory path", error, FALSE);
806 if (g_access (home_dir, F_OK) != 0) {
807 GumConfig *config = NULL;
809 if (!g_file_test (home_dir, G_FILE_TEST_EXISTS)) {
810 g_mkdir_with_parents (home_dir, mode);
813 if (!g_file_test (home_dir, G_FILE_TEST_IS_DIR)) {
814 GUM_RETURN_WITH_ERROR (GUM_ERROR_HOME_DIR_CREATE_FAILURE,
815 "Home directory creation failure", error, FALSE);
818 if (!_set_smack64_attr (home_dir,
819 GUM_CONFIG_GENERAL_SMACK64_USER_FILES)) {
820 GUM_RETURN_WITH_ERROR (GUM_ERROR_FILE_ATTRIBUTE,
821 "Unable to set smack64 home dir attr", error, FALSE);
824 /* when run in test mode, user may not exist */
829 if (chown (home_dir, uid, gid) < 0) {
830 GUM_RETURN_WITH_ERROR (GUM_ERROR_HOME_DIR_CREATE_FAILURE,
831 "Home directory chown failure", error, FALSE);
834 config = gum_config_new (NULL);
835 retval = _copy_dir_recursively (gum_config_get_string (config,
836 GUM_CONFIG_GENERAL_SKEL_DIR), home_dir, uid, gid, umask, error);
837 g_object_unref (config);
845 * gum_file_delete_home_dir:
846 * @dir: (transfer none): the path to the directory
847 * @error: (transfer none): the #GError which is set in case of an error
849 * Deletes the directory and its sub-directories recursively.
851 * Returns: TRUE if successful, FALSE otherwise and @error is set.
854 gum_file_delete_home_dir (
861 if (!dir || !(gdir = g_dir_open(dir, 0, NULL))) {
862 GUM_RETURN_WITH_ERROR (GUM_ERROR_HOME_DIR_DELETE_FAILURE,
863 "Invalid home directory path", error, FALSE);
866 const gchar *fname = NULL;
868 gchar *filepath = NULL;
869 while ((fname = g_dir_read_name (gdir)) != NULL) {
870 if (g_strcmp0 (fname, ".") == 0 ||
871 g_strcmp0 (fname, "..") == 0) {
875 filepath = g_build_filename (dir, fname, NULL);
877 retval = lstat(filepath, &sent);
879 /* recurse the directory */
880 if (S_ISDIR (sent.st_mode)) {
881 retval = (gint)!gum_file_delete_home_dir (filepath, error);
883 retval = g_remove (filepath);
890 GUM_RETURN_WITH_ERROR (GUM_ERROR_HOME_DIR_DELETE_FAILURE,
891 "Unable to delete files in the directory", error, FALSE);
896 if (g_remove (dir) != 0) {
897 GUM_RETURN_WITH_ERROR (GUM_ERROR_HOME_DIR_DELETE_FAILURE,
898 "Unable to delete home directory", error, FALSE);