فبراير 26، 2009

أنواع أنظمة تسيير الملفات على جنو/لينكس و تجربتي الأخيرة مع Ext4 و LVM2

سأتحدث اليوم عن تجربتي الأخيرة مع LVM, EXT4 و القرارات التي يتم إتخادها لإدراج البرمجيات في توزيعات لينوكس.

أولا جنو/لينوكس مبني على حرية الإختيار فبإمكانك كمستخدم أن تتدخل في كل مستويات النظام و كل البرمجيات التي تريد أن تستخدمها. كما أستطيع أن أكد لكم أنه رغم أربعة سنوات من إستخدامي لهذا النظام لم أستطع حتى ضبط الأساسيات، و ذلك ليس لأنه صعب بل لأنه بحر من المعلومات التقنية و الإختيارات.

و لا تنظر إلى هذه الإختيارات على أنها عائق بل هي حلول لمختلف أنواع المتطلبات / الأعمال. فمثلا نفس النظام يمكن أن يستخدم من طرف مُشرف الشبكة، أو أن يُستخدم في مستقبالات القنوات الفضائية أو في طابعات الورق الحديثة، أو في أجهزة الهاتف النقال أو على حاسوبك الشخصي. هذه الأمثلة حقيقية اليوم و هنالك أكثر من هذا.

ثنيا، حتى لو كنت تستخدم فقط أجهزة الحسوب العادية فيمكن أن تختار البرامج و الإعدادات التي تناسب قدرات و مواصفات هذه الأجهزة. فمثلا يمكن أن تقوم بإعداد نواة النظام و باقي البرامج التي تسخدمها لتستفيد من أخر نقطة أداء في معالج حاسوبك (على أساس أن يكون المعالج في هذه الحالة من فئة Multi Cores مثل Intel Core 2 Duo/Quad/I7) أو تستخدم إعدادات تسمح لك بالوصول إلى الحد الأقصى في الأداء في القراص الصلبة. القائمة الطويلة لكن لا أحد يفرض عليك أن تقوم بتعديل كل شيء، يمكنك أن تستخدم الإعدادات الإفتراضية. فقط أردت أن أشير أنه إن كان لك شغف بمعرفة التفاصيل فستجد في منصة جنو/لينوكس كل المساحة الكافية لذلك.

من بين الإختيارات التي يجب عليك أن تقوم بها هي أي نظام تسيير الملفات تريد أن تستخدم. كما لا يشترط أن تستخدم واحد فقط، يمكن أن تمزج بينهم. من أهم أنظمة تسيير الملفات ستجد Ext2، Ext3، و Ext4 ثم JFS, XFS أو Reiserfs3 و قريبا سنرى BtrFs و Tux3. و يمكن إعتبار Ext3 هي الإختيار الإفتراضي في أغلب توزيعات جنو/لينوكس.

سأقوم بشرح مبسط للفرق بينها. أولا Ext3 هي تطور لـ Ext2 بحيث أن أهم خاصية تميزها على هذه الأخيرة هي إستخدام سجل لتسجيل التغييرات (Journaling file system) التي تقع على الملفات الشيء الذي يزيد من درجة الأمان من الأعطال التي قد تصب الملفات في حالة فشل أو إنقطاء الكهرباء. كما أن هذا النظام لا يسبب تجزء الملفات بشكل كبير كما هو معروف في ويندوز (File Fragmentation)

XFS و JFS يتميزين بالسرعة ينصح بهما على أجهزة الحاسوب النقال و الأجهزة القديمة. يتوفرين على مزايا تقنية مهمة خصوصا XFS. كما ينصح بهذه الأخيرة في حالة أردت إستخدام مساحات عملاقة لكنها أكثر عرضة للتجزئة مقارنة مع Ext3.

بالنسبة لReiserFs3 يمكن إعتبارها أنها كانت تحتوي على مجموعة من التقنيات و المزايا التي كانت تجعل منها الإختيار الأنسب إن أردت السرعة، الأداء و الأمان في نفس الوقت، لكن تقلص الفرق. كان سيكون هناك إصدار 4 منها لولا وقوع بعض النزاعات بين مطوره و مطوري نواة لينوكس من جهة و لولا إرتكاب مطوره لجريمة قتل. مازال مشروع Reiser4FS قائما و لكن لا يحضى بتطوير نشيط.

Ext4 تم تفعيله في الإصدار الأخير لنواة لينوكس 2.6.28 و هو تطور لـ Ext3. و من أهم الميزات هذا النظام تحسين الأداء بصفة عامة، مزايا أمان جديدة، قدرة إستعاب كبيرة تصل إلى 1ُExaByte أو مليار جيجابيت.

BtrFS نظام الملفات المستقبلي لمنافسة نظام تسيير الملفات الشهير و المتطور ZFS. سيصدر هذه السنة. سيظم هذا النظام كل الخصائص التي حلم بها المطورون يوما :)

Tux3 يشبه BtrFS في التطور لكن يعطي الأولوية للتقنيات الأكثر إستخداما. يمكن إعتباره BtrFS من الوزن الخفيف.


جيد حتى الآن. فما قصة LVM؟
LVM أو Logical Volume Manager أو مدير الأقراص المنطقي هو عبارة عن خاصية يتم تفعيلها للتعامل مع الأقراص و تجزيئات الأقراص بشكل منطقي. فعلى سبيل المثال إن كان لك قرصين صلبين الأول سعته 80GB و الثاني 160GB و أردت التعامل معهما على شكل قرص صلب واحد بحيث تصبح سعته مشتركة (في هذه الحالة ستظهر المساحة بسعة 240GB) يمكن فعل ذالك من خلال تفعيل LVM.
كما تسهل خاصية LVM التعامل مع الأجزاء Partitions و يصبح من السهل تضخيم و تقليص الأجزاء بكل سهولة و كذلك إضافة أو إزالة الأجزاء بكل سهولة.

شخصيا لا أنصح بها إلا إدا كنت تستخدم RAID و تعي ما تفعله جيدا. كما أنصح بالتأني بعض الوقت قبل المرور إلى Ext4 على الأقل حتى إصدار النواة الجديدة.

حاليا أستخدم Ext4 على القرص الأول و أحتفض بـ Ext3 على القرص الثاني. لم أرى أية مشاكل بل تحسن في أداء القرص الذي يستعمل Ext4. كما ألغيت خاصية LVM2 بعدما رأيت أنها لم تقدم لي أي إمتياز، بل يمكن أن تتسبب في تعقيدات أنا في غنا عنها.

و أخيرا من خلال هذه التجربة الأخيرة أدركت لماذا يختار بعض مطوري توزيعات جنو/لينوكس الإحتفاض ببعض الأجزاء من البرمجيات في الإصدارات السابقة (خصوصا توزعتي Red Hat و CentOS). رغم أنه قليل من هم يدركون هذا و الأغلب يفضل إدراج الإصدارات الجديدة.

أما إن كنت تتساءل عن التوزيعة التي أستخدم و كيف قمت بحل المشكل؟ الجواب هو توزيعة ArchLinux التي تحضى بإدراج دائم لآخر الإصدارات و هو ما أوقعني في ذلك العطل (و الخطأ خطأي). و الحل كان كذلك بإستخدام تقنية عجيبة في هذه التوزيعة و هي الرجوع بمرآة التحديثات إلى الوراء ثم تثبيت الإصدار السابق الذي كان يعمل، ثم تجميده على ذالك الإصدار ثم الرجوع إلى الأمام و تحديث كل شيء. و النتيجة كانت نظام لينوكس بنواة kernel 2.6.28.7 و KDE 4.2 و Xorg 7.3 بدلا من Xorg 7.4

سأكتب عن الطريقة التي يمكن لكم أن تثبتوا بها هذه التوزيعة قريبا.

تحديث 1: عثرت على مقالة تقنية بخصوص Ext4 (بالإنجليزية) يمكن الإطلاع عليها لمن أراد ذلك.
Anatomy of ext4

تحديث 2 (20/03/2009): من الأفضل أن بتم تفادي إستخدام Ext4 للجزء المخصص لـ Home حتى يتم إصدار النواة 2.6.30 و ما فوق.

فبراير 09، 2009

سلسلة البرمجة الرسومية بواسطة باي كيوت PyQt4 للمبتدئين 04

اليوم تنتهي رحلتنا مع الآلة الحاسبة حيث سنضيف اللمسات الأخيرة على الواجهة و الشفرة المصدرية.

بخصوص الواجهة الرسومية سنقوم بحدف الحقلين الأول و الثاني و ستصبح الواجهة كالتالي:



و الشفرة المصدرية أصبحت كالتالي:



import sys

from PyQt4 import QtGui, QtCore, uic

from functools import partial



class CalcDialog(QtGui.QDialog):



    iMathOp = 0

    fstValue = 0

    MathOps = " + ", " - ", " * ", " / "



    def __init__(self, *args):

        QtGui.QWidget.__init__(self, *args)

        uic.loadUi("PyCalc.ui", self)



        NumButtonsList = self.c0, self.c1, self.c2, self.c3, self.c4, self.c5, self.c6, self.c7, self.c8, self.c9

        for x in range(0, len(NumButtonsList)) :

            self.connect(NumButtonsList[x], QtCore.SIGNAL("clicked()"), partial(self.fAddNbr, str(x)))



        MathOpButtonsList = self.opplus, self.opminus, self.opmulti, self.opdiv

        for x in range(0, len(MathOpButtonsList)) :

            self.connect(MathOpButtonsList[x], QtCore.SIGNAL("clicked()"), partial(self.fEntrySwitch, x+1 ))



        self.connect(self.opequal, QtCore.SIGNAL("clicked()"), self.fCalculate)

        self.connect(self.opdot, QtCore.SIGNAL("clicked()"), self.fDecimal)

        self.connect(self.opclear, QtCore.SIGNAL("clicked()"), self.fClear)



    def fAddNbr(self, x):

        self.valresult.setText(self.valresult.text() + x)



    def fEntrySwitch(self, iMathOp):

        self.iMathOp = iMathOp

        self.fstValue = float(self.valresult.text())

        self.valresult.setText('')



    def fDecimal(self):

        x = str(self.valresult.text())

        if x.find('.') == -1:

            self.valresult.setText(self.valresult.text() + '.')



    def fCalculate(self):

        if self.iMathOp >0:

            rCalc = eval("self.fstValue" + self.MathOps[self.iMathOp-1] +  "float(self.valresult.text())")

            self.valresult.setText(str(rCalc))



        self.iMathOp = 0



    def fClear(self):

        self.iMathOp = 0

        self.fstValue = 0

        self.valresult.setText('')



if __name__ == '__main__':

    app = QtGui.QApplication(sys.argv)



    widget = CalcDialog()

    widget.show()

    sys.exit(app.exec_())




الآن سأشرح التغييرات التي طرأت على الشفرة المصدرية:

أولا، لاحظنا في الشفرة المصدرية السابقة (التدوينة الثالثة) أن هنالك عشرة أسطر متعلقة بربط الأزرار بالدالة fAddNbr متشابهة في كل شيء إلا العدد الذي نمرره فكل مرة للدالة.

للتخلص من تلك الأسطر يمكن وضع كل أسماء الأزرار في قائمة، ثم المرور على كل واحد منها بإستخدام حلقة و تنفيد أمر ربط الزر بدالة fAddNbr

 نفس الشيء فعلناه بالنسبة لأزرار العمليات الحسابية.

ثانيا، أصبحت الدالة fAddNbr تقوم بشيء واحد وهو إضافة العدد الجديد إلى العدد المتواجد في الحقل.

ثالثا، قمنا بتعديل دالة fEntrySwitch بحيث إستغنينا عن المتغيرة bSwitch و أضفنا سطر جديد يسمح بالإحتفاض بالعدد الأول في المتغيرة fstValue

رابعا، قمنا بتحديث دالة fDecimal حتى تتمشى مع الوضع الجديد و هو وجود حقل واحد بدلا من إثنين.

خامسا، وقع لدالة fCalculate تغيير مهم و هو كل العمليات الحسابية يتم معالجتها بسطر واحد فقط. بحيث إستخدمنا دالة eval التي تقوم بتقيم العمليات الحسابية من نص إلى قيمة عددية.
فمثلا إن كتبنا "6 * 3" محتفظين بعلامتي "" فنقصد من ذلك أن هذا نصا و ليس عددا، لكن بايثون يتوفر على دالة إسمها eval تستطيع فهم و تقييم ما كتب في ذلك النص و إظهار النتيجة الحسابية.

سادسا تم تحديث دالة fClear حتى تقوم بمسح كل القيم. يضا تمت إعادة تسمية القسم من CalcApp إلى CalcDialog

و بهذا إنتهت هذه السلسلة. من السهل أن نستمر في تطوير هذه الآلة الحاسبة حتى تصبح جد متقدمة لكن هذا ليس من هدف هذه السلسلة. بايثون و كيوت جد متطورين و يوفرين إمكانات ضخمة.

بالنسبة للمبرمجين الذين إعتادوا على VB أو غيره قد يجدون صعوبة في التأقلم مع بايثون و كيوت، لكن الإستفادة التي ستحصلون عليها بعد خوض هذه المغامرة ستكون كبيرة.

و إن كنت تعتقد أنك لن تستطيع البرمجة ببايثون و كيوت يمكن تجربة KBasic أو Gambas إن كانت لك خلفية في VB أو Lazarus إن كنت من مبرمجي Delphi/Pascal. المهم هو أن لينوكس غني بلغات البرمجة و أدواتها.


بالنسبة للمتقدمين في البرمجة في PyQt4 هل هنالك أية ملاحظات أو تحسينات يمكن إضافتها على الشفرة المصدرية؟

تحديث 1 (14-04-2009): يمكن تحميل الشفرة المصدرية من هنا.

فبراير 07، 2009

سلسلة البرمجة الرسومية بواسطة باي كيوت PyQt4 للمبتدئين 03

هدفنا هو أن نجعل الأعداد التي ستكتب في كل من الحقل الأول و الثاني تظهر كنتيجة في الحقل الثالث بعد القيام بالعملية الحسابية المطلوبة.

هنا يجب علينا أن نتدكر أن كل زر و كل حقل لديه اسم خاص به سبق و أن قمنا بتحديده عندما أنشئنا الواجهة الرسومية أول مرة. أيضا سنحاول تقسيم البرنامج إلى أجزاء بسيطة بحيث سيكون كل جزء بمثابة وظيفة متخصصة في شيء محدد.

أولا يجب علينا أن نتصور الطريقة التي يمكن من خلالها التعبير على عدد ما، مثل 125.
إحدى هذه الطرق هي (1x100) + (2x10) + (5x1)
 أو
القيمة السابقة مضروبة في 10 + العدد الجديد. مثال: X=(X*10) + y 
او
أن نعتبر أن كل رقم بمثابة نص يتم إضافته في أخر السطر للقيمة السابقة، مثال: "1"+"2" النتيجة ستكون هي "21"

سأعتمد على الطريقة الأخيرة لبساطتها.

إذن كلما ضغطنا على زر رقم معين سيكتب الرقم الذي يعبر عنه في أخر السطر للقيمة الحالية.
حتى يتمكن كل زر من القيام بذلك يجب أن نحدد ماذا عليه أن يفعل عندما سيتم الضغط عليه. و هذا ما يسمى بالأحداث Events.


الآن سأحاول أن أوضح لكم كيف سيعمل البرنامج بلغة نفهمها نحن ثم بعدها نحولها إلى لغة بايثون:
1. ستظهر الواجهة الرسومية البرنامج.
2. بعدها سيقوم المستخدم بكتابة العدد 935.
3. في كل مرة يتم الضغط على زر ما، يجب عليه أن يقرر في أي حقل (الأول أم الثاني) سيقوم بإضافة العدد الذي عبر عنه إلى العدد المكتوب مسبقا. مثلا إضافة "5" إلى "93"
4. بعد ذلك سيقوم المستخدم بعملية الجمع +
5. تتكرر نفس العملية بالنسبة للعدد الثاني تماما كما في الخطوة الثالثة.
6. عندما يضغط المستخدم على زر = يجب إجراء العملية الحسابية المطلوبة بحيث سيستخدم البرنامج القيمة المتواجدة في الحقل الأول و الثاني ثم يقرر أي نوع من العمليات الحسابية سيقوم بها، ثم يظهر النتيجة في الحقل الثالث.


قبل أن أكتب الشفرة المصدرية سأدكر بأن الأزرار التي تعبر عن 0 حتى 9 إسمها c0 إلى c9
أزرار العمليات الرياضية:
+ opplus
- opminus
* opmulti
/ opdiv
زر = إسمه opequal
زر الفاصلة . opdot
زر المسح opclear
الحقل الأول إسمه value1، الحقل الثاني value2، حقل النتيجة valresult

الشفرة المصدرية للبرنامج هي:




import sys

from PyQt4 import QtGui, QtCore, uic

from functools import partial



class CalcApp(QtGui.QDialog):



    bSwitch = False

    iMathOp = 0



    def __init__(self, *args):

        QtGui.QWidget.__init__(self, *args)

        uic.loadUi("PyCalc.ui", self)



        self.connect(self.c0, QtCore.SIGNAL("clicked()"), partial(self.fAddNbr, '0'))

        self.connect(self.c1, QtCore.SIGNAL("clicked()"), partial(self.fAddNbr, '1'))

        self.connect(self.c2, QtCore.SIGNAL("clicked()"), partial(self.fAddNbr, '2'))

        self.connect(self.c3, QtCore.SIGNAL("clicked()"), partial(self.fAddNbr, '3'))

        self.connect(self.c4, QtCore.SIGNAL("clicked()"), partial(self.fAddNbr, '4'))

        self.connect(self.c5, QtCore.SIGNAL("clicked()"), partial(self.fAddNbr, '5'))

        self.connect(self.c6, QtCore.SIGNAL("clicked()"), partial(self.fAddNbr, '6'))

        self.connect(self.c7, QtCore.SIGNAL("clicked()"), partial(self.fAddNbr, '7'))

        self.connect(self.c8, QtCore.SIGNAL("clicked()"), partial(self.fAddNbr, '8'))

        self.connect(self.c9, QtCore.SIGNAL("clicked()"), partial(self.fAddNbr, '9'))



        self.connect(self.opplus, QtCore.SIGNAL("clicked()"), partial(self.fEntrySwitch, True, 1))

        self.connect(self.opminus, QtCore.SIGNAL("clicked()"), partial(self.fEntrySwitch, True, 2))

        self.connect(self.opmulti, QtCore.SIGNAL("clicked()"), partial(self.fEntrySwitch, True, 3))

        self.connect(self.opdiv, QtCore.SIGNAL("clicked()"), partial(self.fEntrySwitch, True, 4))



        self.connect(self.opequal, QtCore.SIGNAL("clicked()"), self.fCalculate)



        self.connect(self.opdot, QtCore.SIGNAL("clicked()"), self.fDecimal)



        self.connect(self.opclear, QtCore.SIGNAL("clicked()"), self.fClear)





    def fAddNbr(self, x):

        if self.bSwitch == False:

            self.value1.setText(self.value1.text() + x)

        else:

            self.value2.setText(self.value2.text() + x)





    def fEntrySwitch(self, bSwitch,  iMathOp):

        self.bSwitch = bSwitch

        self.iMathOp = iMathOp





    def fDecimal(self):

        if self.bSwitch == False:

            x = str(self.value1.text())

            v = self.value1

        else:

            x = str(self.value2.text())

            v = self.value2



        if x.find('.') == -1:

            v.setText(v.text() + '.')





    def fCalculate(self):

        if self.iMathOp == 1 :

            rCalc = float(self.value1.text()) + float(self.value2.text())

        elif self.iMathOp == 2:

            rCalc = float(self.value1.text()) - float(self.value2.text())

        elif self.iMathOp == 3:

            rCalc = float(self.value1.text()) * float(self.value2.text())

        elif self.iMathOp == 4:

            rCalc = float(self.value1.text()) / float(self.value2.text())

        else:

            return None



        self.valresult.setText(str(rCalc))





    def fClear(self):

        self.bSwitch = False

        self.iMathOp = 0

        self.value1.setText('')

        self.value2.setText('')

        self.valresult.setText('')





if __name__ == '__main__':

    app = QtGui.QApplication(sys.argv)



    widget = CalcApp()

    widget.show()



    sys.exit(app.exec_())





بالنسبة للتغيرات التي حصلت فهي كالتالي:
1. تم إستيراد دالة partial من قسم functools. سيتم الإستعانة بهذه الدالة لربط بين الدوال التي سنضيفها نحن و بين الأحداث (الضغط على الأزرار)
2. تم الإستعانة بمتغيرتين bSwitch التي سنستخدمها لتحديد الحقل الذي سيكتب فيه العدد، و iMathOp لتحديد نوع العملية الحسابية المطلوبة.
3. تم إضافة دالة fAddNbr لتقوم بمهمة إضافة العدد الذي ضغط عليه المستخدم إلى العدد الموجود سابقا، و هذه الدالة قادرة على معرفة أي حقل ستستخدم إعتمادا على متغيرة bSwitch
4. تم إضافة دالة fEntrySwitch التي ستقوم بتغير قيمة المتغيرة bSwitch إلى قيمة صحيح أو خطأ و  بتغير قيمة iMathOp إلى قيمة عددية من 1 إلى 4
5. دالة fCalculate التي ستقوم بمعرفة العملية الحسابية المطلوبة إعتمادا على قيمة المتغيرة iMathOp ثم تقوم بإجرائها على قيمة الحقل الأول و الثاني، ثم تضع النتيجة في الحقل الثالث.
6. دالة fClear التي تقوم بمسح محتوى الحقول و تغير قيمة المتغيرتين إلى القيمة المفترضة.
7. دالة fDecimal وظيفتها هو إضافة الفاصلة إلى العدد المتواجد في الحقل الأول أو الثاني، ثم التأكد من عدم إسناد أكثر من فاصلة واحدة إلى نفس العدد.
8. عودة إلى الدالة __init__ و التي سيتم تعديلها حتى تقوم:
8.1. بربط كل زر من أزرار الأعداد بدالة fAddNbr و بالتالي نحصل على وظيفة كتابة الأعداد في الحقلين الأول و الثاني.
8.2. بربط كل زر من أزرار العمليات الحسابية بدالة fEntrySwitch و بالتالي يستشعر البرنامج تغير قيمة المتغيرتين bSwitch و iMathOp
8.3. ربط زر يساوي = بدالة fCalculate التي ستتكلف بحساب النتيجة و وضعها في الحقل الثالث.
8.4. ربط زر الفاصلة بدالة fDecimal التي خصصناها إلى التحكم في الفاصلة.
8.5. ربط زر المسح C بدالة fClear لمسح و إعادة القيم في الحقول و المتغيرتين إلى القيم المفترضة

و بالتالي نحصل على آلة حاسبة قادرة على إجراء عمليات حسابية بسيطة. في التدوينة القادمة سنحاول أن نجعل الآلة الحاسبة تعتمد على حقل واحد كما سندخل بعض التعديلات البسيطة على الشفرة المصدرية.

يالنسبة للأصدقاء الأكثر خبرة في PyQt4 هل لذيكم أي ملاحظة على الطريقة التي إعتمدتها و الشفرة المصدرية التي كتبتها؟

تحديث 1 (14-04-2009): يمكن تحميل الشفرة المصدرية من هنا.

فبراير 03، 2009

نسخ و حرق الأقراص المدمجة عبر سطر الأوامر على جنو/لينكس

يتوفر نظام جنو/لينوكس على مجموعة من البرامج المكلفة بنسخ و حرق الأقراص المدمجة (CD/DVD Burning Software) أشهرها K3B و Brasero. غير أن الإمتياز الكبير لجنو/لينوكس هو أنه يمكن فعل كل ما تريد إنطلاقا من سطر الأوامر دون الحاجة إلى واجهة رسومية :)

مؤخرا إحتجت إلى نسخ مجموعة من الأقراص المدمجة التي على ما يبدوا تعرضت إلى نوع من التلف أو حرقت بطريقة غير عادية. لم أجد إلا أدات cdrdao قادرة على فعل ذلك.

الطريقة جد بسيطة حيث تحتاج إلى كتابة سطرين فقط:
الأول: الحصول على عنوان حارق الأقراص المدمجة الذي ستستخدمه لذلك
cdrdao  scanbus
=>
ATA:1,0,0    HL-DT-ST, DVDRAM GSA-4167B, DL13
ATA:1,1,0    HL-DT-ST, CD-RW GCE-8526B , 1.04

الثاني: تكوين نسخة طبق الأصل و حرقها كما هو الحال عند إستخدام CloneCD على نظام ويندوز.

cdrdao  copy  --speed 36  --device ATA:1,1,0


أيضا، إن كنت من مستخدمي AcetoneISO و أردت حرق صور لأقراص مدمجة من نوع toc يكفي تحديد إسم الملف و إستخدام الأمر التالي:

cdrdao  write   --speed 36  --device ATA:1,1,0  AcetoneISO_cdAudio.bin.toc



للمزيد من المعلومات بخصوص نسخ و حرق الأقراص المدمجة أنظر هنا و هنا.